placementt-core 1.300.1055 → 1.300.1057

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/index.d.ts CHANGED
@@ -29,6 +29,7 @@ export * from "./tutorialTips";
29
29
  export * from "./constants";
30
30
  export * from "./dataViewerReload";
31
31
  export * from "./hooks";
32
+ export * from "./workflowBands";
32
33
  export * from "./util";
33
34
  export * from "./images/GatsbyBenchmarks";
34
35
  export * from "./databaseTypes";
package/lib/index.js CHANGED
@@ -49,6 +49,7 @@ __exportStar(require("./tutorialTips"), exports);
49
49
  __exportStar(require("./constants"), exports);
50
50
  __exportStar(require("./dataViewerReload"), exports);
51
51
  __exportStar(require("./hooks"), exports);
52
+ __exportStar(require("./workflowBands"), exports);
52
53
  __exportStar(require("./util"), exports);
53
54
  __exportStar(require("./images/GatsbyBenchmarks"), exports);
54
55
  __exportStar(require("./databaseTypes"), exports);
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AACA,sDAAoC;AACpC,gEAA8C;AAC9C,0DAAwC;AACxC,2DAAyC;AACzC,kDAAgC;AAChC,uDAAqC;AACrC,4DAA0C;AAC1C,2DAAyC;AAEzC,oDAAkC;AAClC,8CAA4B;AAC5B,kDAAgC;AAChC,kDAAgC;AAChC,qDAAmC;AACnC,mDAAiC;AACjC,qDAAmC;AACnC,mDAAiC;AACjC,+CAA6B;AAC7B,wCAAsB;AACtB,oDAAkC;AAClC,4CAA0B;AAC1B,2CAAyB;AACzB,kDAAgC;AAChC,uDAAqC;AACrC,kDAAgC;AAChC,iDAA+B;AAC/B,2DAAyC;AACzC,uDAAqC;AACrC,iDAA+B;AAC/B,8CAA4B;AAC5B,qDAAmC;AACnC,0CAAwB;AACxB,yCAAuB;AACvB,4DAA0C;AAC1C,kDAAgC;AAChC,iDAA+B;AAC/B,2CAAyB;AACzB,4DAA0C;AAC1C,oEAAiD;AACjD,yEAAuD;AACvD,kEAAgD;AAChD,kEAAgD;AAChD,oEAAkD;AAClD,yGAAuF;AACvF,0GAAwF;AACxF,+FAA6E;AAC7E,4DAA0C;AAC1C,oEAAkD;AAClD,iEAA+C;AAC/C,gEAA8C;AAC9C,2EAAyD;AACzD,6CAA2B;AAC3B,+CAA6B;AAC7B,8CAA4B;AAC5B,6DAAuE;AAA9D,2IAAA,OAAO,OAAuB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AACA,sDAAoC;AACpC,gEAA8C;AAC9C,0DAAwC;AACxC,2DAAyC;AACzC,kDAAgC;AAChC,uDAAqC;AACrC,4DAA0C;AAC1C,2DAAyC;AAEzC,oDAAkC;AAClC,8CAA4B;AAC5B,kDAAgC;AAChC,kDAAgC;AAChC,qDAAmC;AACnC,mDAAiC;AACjC,qDAAmC;AACnC,mDAAiC;AACjC,+CAA6B;AAC7B,wCAAsB;AACtB,oDAAkC;AAClC,4CAA0B;AAC1B,2CAAyB;AACzB,kDAAgC;AAChC,uDAAqC;AACrC,kDAAgC;AAChC,iDAA+B;AAC/B,2DAAyC;AACzC,uDAAqC;AACrC,iDAA+B;AAC/B,8CAA4B;AAC5B,qDAAmC;AACnC,0CAAwB;AACxB,kDAAgC;AAChC,yCAAuB;AACvB,4DAA0C;AAC1C,kDAAgC;AAChC,iDAA+B;AAC/B,2CAAyB;AACzB,4DAA0C;AAC1C,oEAAiD;AACjD,yEAAuD;AACvD,kEAAgD;AAChD,kEAAgD;AAChD,oEAAkD;AAClD,yGAAuF;AACvF,0GAAwF;AACxF,+FAA6E;AAC7E,4DAA0C;AAC1C,oEAAkD;AAClD,iEAA+C;AAC/C,gEAA8C;AAC9C,2EAAyD;AACzD,6CAA2B;AAC3B,+CAA6B;AAC7B,8CAA4B;AAC5B,6DAAuE;AAA9D,2IAAA,OAAO,OAAuB"}
package/lib/pricing.d.ts CHANGED
@@ -12,6 +12,10 @@
12
12
  * a partial-product discount — that is how the pricing sheet has it, and it is
13
13
  * why `partialProductDiscount` names those two rather than being one number.
14
14
  *
15
+ * A trust is quoted as one account on its combined roll, and earns a discount
16
+ * on the whole quote for the number of schools it brings on — a smaller one
17
+ * below the threshold, a larger one at or above it.
18
+ *
15
19
  * Every figure lives in `PricingConfig` so it can be stored in Firestore and
16
20
  * adjusted without a deploy. `DEFAULT_PRICING_CONFIG` is the fallback and the
17
21
  * seed for that document.
@@ -43,6 +47,15 @@ export type PricingConfig = {
43
47
  };
44
48
  /** Taken off the whole quote when a school takes the full platform. */
45
49
  fullPlatformDiscount: number;
50
+ /** Taken off the whole quote for a trust, on how many schools it brings. */
51
+ trust: {
52
+ /** Schools at which the larger discount takes over. */
53
+ threshold: number;
54
+ /** Fraction off a trust below the threshold. */
55
+ discount: number;
56
+ /** Fraction off a trust at or above it. */
57
+ largeDiscount: number;
58
+ };
46
59
  /** Careers hubs are a flat platform fee plus a fee for each school. */
47
60
  hub: {
48
61
  baseFee: number;
@@ -68,6 +81,8 @@ export type SchoolPricingInput = {
68
81
  destinationsDiscount?: boolean;
69
82
  /** Discount the whole quote, for a school taking the full platform. */
70
83
  fullPlatformDiscount?: boolean;
84
+ /** Schools in the trust being quoted. Two or more earns a trust discount. */
85
+ schools?: number;
71
86
  /** A negotiated discount off the whole quote, as a fraction (0.1 = 10% off).
72
87
  * Taken after the full-platform discount and before the minimum fee. */
73
88
  bespokeDiscount?: number;
@@ -101,7 +116,11 @@ export type SchoolPricingResult = {
101
116
  subtotal: number;
102
117
  /** £ taken off the subtotal by the full-platform discount. */
103
118
  fullPlatformDiscountApplied: number;
104
- /** £ taken off by the negotiated discount, after the full-platform one. */
119
+ /** £ taken off by the trust discount, after the full-platform one. */
120
+ trustDiscountApplied: number;
121
+ /** The rate that trust discount was taken at, as a fraction. */
122
+ trustDiscountRate: number;
123
+ /** £ taken off by the negotiated discount, after the trust one. */
105
124
  bespokeDiscountApplied: number;
106
125
  /** True when the minimum fee, not the products, set the price. */
107
126
  minFeeApplied: boolean;
@@ -115,6 +134,17 @@ export type HubPricingResult = {
115
134
  schoolFees: number;
116
135
  total: number;
117
136
  };
137
+ /**
138
+ * What a trust comes off its quote for its size.
139
+ *
140
+ * A trust is more than one school, so a count below two is a single school and
141
+ * earns nothing — the discount is for bringing schools with you.
142
+ *
143
+ * @param {number} [schools] schools in the trust.
144
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
145
+ * @return {number} the discount, as a fraction of the quote.
146
+ */
147
+ export declare function trustDiscountRate(schools?: number, config?: PricingConfig): number;
118
148
  /**
119
149
  * Price a school, trust or hub-school quote.
120
150
  *
@@ -165,6 +195,8 @@ export type InstitutePricing = {
165
195
  discountReason?: string;
166
196
  /** A price agreed with the customer, which replaces the calculated one. */
167
197
  quoteOverride?: number;
198
+ /** Schools in the trust, which set its discount. Trust accounts only. */
199
+ schools?: number;
168
200
  /** Schools on the hub. Hub accounts only. */
169
201
  hubSchools?: number;
170
202
  /** The quote as last saved, for the customer to see. */
@@ -178,6 +210,9 @@ export type InstituteQuote = {
178
210
  /** The lines added up, before any whole-quote discount. */
179
211
  subtotal: number;
180
212
  fullPlatformDiscountApplied: number;
213
+ trustDiscountApplied: number;
214
+ /** The rate the trust discount was taken at, as a fraction. */
215
+ trustDiscountRate: number;
181
216
  bespokeDiscountApplied: number;
182
217
  minFeeApplied: boolean;
183
218
  overrideApplied: boolean;
@@ -209,9 +244,12 @@ export type InstituteQuoteInput = {
209
244
  events?: boolean;
210
245
  destinations?: boolean;
211
246
  isHub?: boolean;
247
+ isTrust?: boolean;
212
248
  };
213
249
  /** The institute's roll, when the agreed terms don't name one. */
214
250
  students?: number;
251
+ /** Schools in the trust, when the agreed terms don't name a number. */
252
+ schools?: number;
215
253
  /** Schools on the hub, when the agreed terms don't name a number. */
216
254
  hubSchools?: number;
217
255
  /** The terms agreed with this customer. */
@@ -243,7 +281,8 @@ export declare function hubQuote(schools: number, pricing?: InstitutePricing, co
243
281
  *
244
282
  * The products come from `resolveProducts` rather than the raw flags, so a trust
245
283
  * is priced on the union of its schools' products the same way it is gated on
246
- * them. A year group with no placements agreed is a year group not on work
284
+ * them, and it is a trust — not any school with a number against it — that earns
285
+ * the trust discount. A year group with no placements agreed is a year group not on work
247
286
  * experience — except where the institute has work experience and neither year
248
287
  * group has been filled in, which is a quote waiting to be finished rather than
249
288
  * a school not taking the product, so Y10 is charged at zero to say so.
package/lib/pricing.js CHANGED
@@ -13,12 +13,17 @@
13
13
  * a partial-product discount — that is how the pricing sheet has it, and it is
14
14
  * why `partialProductDiscount` names those two rather than being one number.
15
15
  *
16
+ * A trust is quoted as one account on its combined roll, and earns a discount
17
+ * on the whole quote for the number of schools it brings on — a smaller one
18
+ * below the threshold, a larger one at or above it.
19
+ *
16
20
  * Every figure lives in `PricingConfig` so it can be stored in Firestore and
17
21
  * adjusted without a deploy. `DEFAULT_PRICING_CONFIG` is the fallback and the
18
22
  * seed for that document.
19
23
  */
20
24
  Object.defineProperty(exports, "__esModule", { value: true });
21
25
  exports.PRICING_CONFIG_PATH = exports.DEFAULT_PRICING_CONFIG = void 0;
26
+ exports.trustDiscountRate = trustDiscountRate;
22
27
  exports.calculateSchoolPrice = calculateSchoolPrice;
23
28
  exports.calculateHubPrice = calculateHubPrice;
24
29
  exports.mergePricingConfig = mergePricingConfig;
@@ -37,6 +42,7 @@ exports.DEFAULT_PRICING_CONFIG = {
37
42
  ],
38
43
  partialProductDiscount: { events: 0.2, destinations: 0.2 },
39
44
  fullPlatformDiscount: 0.2,
45
+ trust: { threshold: 10, discount: 0.05, largeDiscount: 0.1 },
40
46
  hub: { baseFee: 2000, perSchoolFee: 150 },
41
47
  };
42
48
  /** Where the adjustable copy of the config lives. */
@@ -79,6 +85,22 @@ function graduated(quantity, bands) {
79
85
  const sum = (values) => values.reduce((total, value) => total + value, 0);
80
86
  const clampFraction = (value) => Math.min(Math.max(value || 0, 0), 1);
81
87
  const bandsTotal = (bands) => sum(bands.map((b) => b.amount));
88
+ /**
89
+ * What a trust comes off its quote for its size.
90
+ *
91
+ * A trust is more than one school, so a count below two is a single school and
92
+ * earns nothing — the discount is for bringing schools with you.
93
+ *
94
+ * @param {number} [schools] schools in the trust.
95
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
96
+ * @return {number} the discount, as a fraction of the quote.
97
+ */
98
+ function trustDiscountRate(schools, config = exports.DEFAULT_PRICING_CONFIG) {
99
+ const count = schools || 0;
100
+ if (count < 2)
101
+ return 0;
102
+ return count >= config.trust.threshold ? config.trust.largeDiscount : config.trust.discount;
103
+ }
82
104
  /**
83
105
  * Price a school, trust or hub-school quote.
84
106
  *
@@ -115,14 +137,18 @@ function calculateSchoolPrice(input, config = exports.DEFAULT_PRICING_CONFIG) {
115
137
  addPerStudent("destinations", "Destinations", input.destinations, input.destinationsDiscount);
116
138
  const subtotal = sum(lines.map((l) => l.amount));
117
139
  const discounted = input.fullPlatformDiscount ? subtotal * (1 - config.fullPlatformDiscount) : subtotal;
118
- const negotiated = discounted * (1 - clampFraction(input.bespokeDiscount));
140
+ const trustRate = trustDiscountRate(input.schools, config);
141
+ const trusted = discounted * (1 - trustRate);
142
+ const negotiated = trusted * (1 - clampFraction(input.bespokeDiscount));
119
143
  const floored = Math.max(negotiated, config.minFee);
120
144
  const overridden = input.quoteOverride !== undefined && input.quoteOverride >= 0;
121
145
  return {
122
146
  lines,
123
147
  subtotal,
124
148
  fullPlatformDiscountApplied: subtotal - discounted,
125
- bespokeDiscountApplied: discounted - negotiated,
149
+ trustDiscountApplied: discounted - trusted,
150
+ trustDiscountRate: trustRate,
151
+ bespokeDiscountApplied: trusted - negotiated,
126
152
  // An agreed price is the price, so neither the floor nor the discounts
127
153
  // are what set it — they are only reported when the calculation stands.
128
154
  minFeeApplied: !overridden && floored > negotiated,
@@ -161,6 +187,7 @@ function mergePricingConfig(stored) {
161
187
  tiers: stored.tiers?.length ? stored.tiers.map((t, i) => ({ ...(d.tiers[i] || d.tiers[d.tiers.length - 1]), ...t })) : d.tiers,
162
188
  partialProductDiscount: { ...d.partialProductDiscount, ...stored.partialProductDiscount },
163
189
  fullPlatformDiscount: stored.fullPlatformDiscount ?? d.fullPlatformDiscount,
190
+ trust: { ...d.trust, ...stored.trust },
164
191
  hub: { ...d.hub, ...stored.hub },
165
192
  };
166
193
  }
@@ -207,6 +234,9 @@ function hubQuote(schools, pricing, config = exports.DEFAULT_PRICING_CONFIG) {
207
234
  lines,
208
235
  subtotal: hub.total,
209
236
  fullPlatformDiscountApplied: 0,
237
+ // A hub is already priced on its schools, so it has no trust discount.
238
+ trustDiscountApplied: 0,
239
+ trustDiscountRate: 0,
210
240
  bespokeDiscountApplied: hub.total - negotiated,
211
241
  minFeeApplied: false,
212
242
  overrideApplied: overridden,
@@ -218,7 +248,8 @@ function hubQuote(schools, pricing, config = exports.DEFAULT_PRICING_CONFIG) {
218
248
  *
219
249
  * The products come from `resolveProducts` rather than the raw flags, so a trust
220
250
  * is priced on the union of its schools' products the same way it is gated on
221
- * them. A year group with no placements agreed is a year group not on work
251
+ * them, and it is a trust — not any school with a number against it — that earns
252
+ * the trust discount. A year group with no placements agreed is a year group not on work
222
253
  * experience — except where the institute has work experience and neither year
223
254
  * group has been filled in, which is a quote waiting to be finished rather than
224
255
  * a school not taking the product, so Y10 is charged at zero to say so.
@@ -236,6 +267,9 @@ function instituteQuote(input, config = exports.DEFAULT_PRICING_CONFIG) {
236
267
  const noYearGroupQuoted = pricing?.wexY10 === undefined && pricing?.wexY12 === undefined;
237
268
  return schoolQuote({
238
269
  students: pricing?.students ?? input.students,
270
+ // Only a trust is quoted on a school count, so a single school can never
271
+ // pick up the discount by having a stray number agreed against it.
272
+ schools: input.products.isTrust ? (pricing?.schools ?? input.schools) : undefined,
239
273
  wexY10: wex ? (noYearGroupQuoted ? 0 : pricing?.wexY10) : undefined,
240
274
  wexY12: wex ? pricing?.wexY12 : undefined,
241
275
  events: Boolean(input.products.events),
@@ -1 +1 @@
1
- {"version":3,"file":"pricing.js","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pricing.js","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;;AA0LH,8CAOC;AAYD,oDAoDC;AASD,8CAMC;AAYD,gDAaC;AAkGD,kCAKC;AAcD,4BAoCC;AAiBD,wCA4BC;AAYD,sCAyBC;AA5eD,uDAAuD;AAC1C,QAAA,sBAAsB,GAAkB;IACjD,MAAM,EAAE,GAAG;IACX,OAAO,EAAE,GAAG;IACZ,KAAK,EAAE;QACH,EAAC,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAC;QACxE,EAAC,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAC;QACzE,EAAC,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAC;KAChF;IACD,sBAAsB,EAAE,EAAC,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAC;IACxD,oBAAoB,EAAE,GAAG;IACzB,KAAK,EAAE,EAAC,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,EAAC;IAC1D,GAAG,EAAE,EAAC,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAC;CAC1C,CAAC;AAEF,qDAAqD;AACxC,QAAA,mBAAmB,GAAG,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;AA6E9D;;;;;;;GAOG;AACH,SAAS,SAAS,CAAC,QAAgB,EAAE,KAAsC;IACvE,IAAI,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAEhD,MAAM,KAAK,GAAkB,EAAE,CAAC;IAChC,IAAI,aAAa,GAAG,CAAC,CAAC;IAEtB,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,aAAa,EAAE,CAAC,CAAC,CAAC;QAC3E,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC;QAC3B,IAAI,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,EAAC,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,IAAI,EAAC,CAAC,CAAC;IACjH,CAAC,CAAC,CAAC;IAEH,+EAA+E;IAC/E,iEAAiE;IACjE,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACpD,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QACf,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC;QAC7D,IAAI,SAAS,EAAE,CAAC;YACZ,SAAS,CAAC,QAAQ,IAAI,QAAQ,CAAC;YAC/B,SAAS,CAAC,MAAM,IAAI,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;QAC7C,CAAC;aAAM,CAAC;YACJ,KAAK,CAAC,IAAI,CAAC,EAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAC,CAAC,CAAC;QACxG,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,MAAM,GAAG,GAAG,CAAC,MAAgB,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC;AAEpF,MAAM,aAAa,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAE/E,MAAM,UAAU,GAAG,CAAC,KAAoB,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAE7E;;;;;;;;;GASG;AACH,SAAgB,iBAAiB,CAC7B,OAAgB,EAChB,SAAwB,8BAAsB;IAE9C,MAAM,KAAK,GAAG,OAAO,IAAI,CAAC,CAAC;IAC3B,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACxB,OAAO,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC;AAChG,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,oBAAoB,CAChC,KAAyB,EACzB,SAAwB,8BAAsB;IAE9C,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAE3B,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAC,KAAK,EAAE,CAAC,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,EAAC,CAAC,CAAC,CAAC;IAC5E,MAAM,UAAU,GAAG,CAAC,GAA4B,EAAE,EAAE,CAChD,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,EAAC,CAAC,CAAC,CAAC;IAE1D,MAAM,KAAK,GAAkB,CAAC,EAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,EAAC,CAAC,CAAC;IAEnG,MAAM,MAAM,GAAG,CAAC,GAAsB,EAAE,KAAa,EAAE,UAAmB,EAAE,EAAE;QAC1E,IAAI,UAAU,KAAK,SAAS;YAAE,OAAO;QACrC,MAAM,KAAK,GAAG,SAAS,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;QAClD,KAAK,CAAC,IAAI,CAAC,EAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,KAAK,EAAC,CAAC,CAAC;IAC/D,CAAC,CAAC;IACF,MAAM,CAAC,QAAQ,EAAE,uBAAuB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACxD,MAAM,CAAC,QAAQ,EAAE,uBAAuB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAExD,MAAM,aAAa,GAAG,CAAC,GAA4B,EAAE,KAAa,EAAE,KAAe,EAAE,UAAoB,EAAE,EAAE;QACzG,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QACnD,MAAM,YAAY,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;QACnG,KAAK,CAAC,IAAI,CAAC,EAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAC,YAAY,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAC,CAAC,CAAC;IACnF,CAAC,CAAC;IACF,aAAa,CAAC,QAAQ,EAAE,iBAAiB,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;IAC/E,aAAa,CAAC,cAAc,EAAE,cAAc,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,oBAAoB,CAAC,CAAC;IAE9F,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,KAAK,CAAC,oBAAoB,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACxG,MAAM,SAAS,GAAG,iBAAiB,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC3D,MAAM,OAAO,GAAG,UAAU,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,OAAO,GAAG,CAAC,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;IACxE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACpD,MAAM,UAAU,GAAG,KAAK,CAAC,aAAa,KAAK,SAAS,IAAI,KAAK,CAAC,aAAa,IAAI,CAAC,CAAC;IAEjF,OAAO;QACH,KAAK;QACL,QAAQ;QACR,2BAA2B,EAAE,QAAQ,GAAG,UAAU;QAClD,oBAAoB,EAAE,UAAU,GAAG,OAAO;QAC1C,iBAAiB,EAAE,SAAS;QAC5B,sBAAsB,EAAE,OAAO,GAAG,UAAU;QAC5C,uEAAuE;QACvE,wEAAwE;QACxE,aAAa,EAAE,CAAC,UAAU,IAAI,OAAO,GAAG,UAAU;QAClD,eAAe,EAAE,UAAU;QAC3B,KAAK,EAAE,UAAU,CAAC,CAAC,CAAE,KAAK,CAAC,aAAwB,CAAC,CAAC,CAAC,OAAO;KAChE,CAAC;AACN,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,iBAAiB,CAC7B,OAAe,EACf,SAAwB,8BAAsB;IAE9C,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC;IACvE,OAAO,EAAC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,GAAG,UAAU,EAAC,CAAC;AAC7F,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,kBAAkB,CAAC,MAA+B;IAC9D,MAAM,CAAC,GAAG,8BAAsB,CAAC;IACjC,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IAEtB,OAAO;QACH,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM;QACjC,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO;QACpC,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK;QAC5H,sBAAsB,EAAE,EAAC,GAAG,CAAC,CAAC,sBAAsB,EAAE,GAAG,MAAM,CAAC,sBAAsB,EAAC;QACvF,oBAAoB,EAAE,MAAM,CAAC,oBAAoB,IAAI,CAAC,CAAC,oBAAoB;QAC3E,KAAK,EAAE,EAAC,GAAG,CAAC,CAAC,KAAK,EAAE,GAAG,MAAM,CAAC,KAAK,EAAC;QACpC,GAAG,EAAE,EAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,GAAG,EAAC;KACjC,CAAC;AACN,CAAC;AA2FD;;;;;;GAMG;AACH,SAAgB,WAAW,CACvB,KAAyB,EACzB,SAAwB,8BAAsB;IAE9C,OAAO,EAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,oBAAoB,CAAC,KAAK,EAAE,MAAM,CAAC,EAAC,CAAC;AACpE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,QAAQ,CACpB,OAAe,EACf,OAA0B,EAC1B,SAAwB,8BAAsB;IAE9C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAE7C,MAAM,KAAK,GAAkB;QACzB,EAAC,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,EAAC;QACvE;YACI,GAAG,EAAE,YAAY;YACjB,KAAK,EAAE,aAAa;YACpB,MAAM,EAAE,GAAG,CAAC,UAAU;YACtB,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;gBACd,CAAC,EAAC,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,EAAC,CAAC,CAAC,CAAC;gBACrF,EAAE;SACT;KACJ,CAAC;IAEF,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,aAAa,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC;IAC7E,MAAM,UAAU,GAAG,OAAO,EAAE,aAAa,KAAK,SAAS,IAAI,OAAO,CAAC,aAAa,IAAI,CAAC,CAAC;IAEtF,OAAO;QACH,IAAI,EAAE,KAAK;QACX,KAAK;QACL,QAAQ,EAAE,GAAG,CAAC,KAAK;QACnB,2BAA2B,EAAE,CAAC;QAC9B,uEAAuE;QACvE,oBAAoB,EAAE,CAAC;QACvB,iBAAiB,EAAE,CAAC;QACpB,sBAAsB,EAAE,GAAG,CAAC,KAAK,GAAG,UAAU;QAC9C,aAAa,EAAE,KAAK;QACpB,eAAe,EAAE,UAAU;QAC3B,KAAK,EAAE,UAAU,CAAC,CAAC,CAAE,OAAO,EAAE,aAAwB,CAAC,CAAC,CAAC,UAAU;KACtE,CAAC;AACN,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAgB,cAAc,CAC1B,KAA0B,EAC1B,SAAwB,8BAAsB;IAE9C,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAE9B,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACvB,OAAO,QAAQ,CAAC,OAAO,EAAE,UAAU,IAAI,KAAK,CAAC,UAAU,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACnF,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,iBAAiB,GAAG,OAAO,EAAE,MAAM,KAAK,SAAS,IAAI,OAAO,EAAE,MAAM,KAAK,SAAS,CAAC;IAEzF,OAAO,WAAW,CAAC;QACf,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,KAAK,CAAC,QAAQ;QAC7C,yEAAyE;QACzE,mEAAmE;QACnE,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;QACjF,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS;QACnE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS;QACzC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;QACtC,cAAc,EAAE,OAAO,EAAE,cAAc;QACvC,YAAY,EAAE,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC;QAClD,oBAAoB,EAAE,OAAO,EAAE,oBAAoB;QACnD,oBAAoB,EAAE,OAAO,EAAE,oBAAoB;QACnD,eAAe,EAAE,OAAO,EAAE,eAAe;QACzC,aAAa,EAAE,OAAO,EAAE,aAAa;KACxC,EAAE,MAAM,CAAC,CAAC;AACf,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,aAAa,CAAC,KAAqB;IAC/C,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC;IAC7D,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC;IAEpE,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC3B,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YAC3B,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,MAAM,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM;SACzD,CAAC,CAAC,CAAC,CAAC;QACL,CAAC,EAAC,GAAG,EAAE,MAAwB,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,EAAC,CAAC,CAAC;IAE3E,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAEjD,OAAO;QACH,KAAK;QACL,QAAQ;QACR,yEAAyE;QACzE,uEAAuE;QACvE,kEAAkE;QAClE,aAAa,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;QAClD,OAAO,EAAE,KAAK,CAAC,KAAK,GAAG,QAAQ;QAC/B,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAClD,CAAC;AACN,CAAC"}
@@ -116,6 +116,43 @@ describe("mergePricingConfig", () => {
116
116
  expect(merged.tiers[2]).toEqual(pricing_1.DEFAULT_PRICING_CONFIG.tiers[2]);
117
117
  });
118
118
  });
119
+ describe("the trust discount", () => {
120
+ const trust = (schools) => ({ students: 10000, events: true, destinations: true, schools });
121
+ it("gives a trust under ten schools 5% off the whole quote", () => {
122
+ const result = (0, pricing_1.calculateSchoolPrice)(trust(9));
123
+ const plain = (0, pricing_1.calculateSchoolPrice)({ ...trust(9), schools: undefined });
124
+ expect(result.trustDiscountRate).toBe(0.05);
125
+ expect(result.total).toBeCloseTo(plain.total * 0.95, 6);
126
+ expect(result.trustDiscountApplied).toBeCloseTo(plain.total * 0.05, 6);
127
+ });
128
+ it("gives a trust of ten schools or more 10%", () => {
129
+ expect((0, pricing_1.calculateSchoolPrice)(trust(10)).trustDiscountRate).toBe(0.1);
130
+ expect((0, pricing_1.calculateSchoolPrice)(trust(40)).trustDiscountRate).toBe(0.1);
131
+ });
132
+ it("gives a single school nothing, however it is counted", () => {
133
+ expect((0, pricing_1.trustDiscountRate)(undefined)).toBe(0);
134
+ expect((0, pricing_1.trustDiscountRate)(0)).toBe(0);
135
+ expect((0, pricing_1.trustDiscountRate)(1)).toBe(0);
136
+ });
137
+ it("is taken after the full-platform discount and before the negotiated one", () => {
138
+ const input = {
139
+ students: 10000, wexY10: 500, events: true, destinations: true,
140
+ fullPlatformDiscount: true, schools: 12, bespokeDiscount: 0.1,
141
+ };
142
+ const result = (0, pricing_1.calculateSchoolPrice)(input);
143
+ const expected = result.subtotal * 0.8 * 0.9 * 0.9;
144
+ expect(result.total).toBeCloseTo(expected, 6);
145
+ expect(result.trustDiscountApplied).toBeCloseTo(result.subtotal * 0.8 * 0.1, 6);
146
+ });
147
+ it("only reaches an institute that is actually a trust", () => {
148
+ const priced = (isTrust) => (0, pricing_1.instituteQuote)({
149
+ products: { events: true, isTrust },
150
+ students: 10000,
151
+ pricing: { schools: 12 },
152
+ }).total;
153
+ expect(priced(true)).toBeCloseTo(priced(false) * 0.9, 6);
154
+ });
155
+ });
119
156
  describe("the negotiated terms", () => {
120
157
  it("takes the bespoke discount after the full-platform one", () => {
121
158
  const base = { students: 2000, events: true, destinations: true, fullPlatformDiscount: true };
@@ -1 +1 @@
1
- {"version":3,"file":"pricing.test.js","sourceRoot":"","sources":["../src/pricing.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pricing.test.js","sourceRoot":"","sources":["../src/pricing.test.ts"],"names":[],"mappings":"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"}
@@ -48,6 +48,10 @@ export type Products = "institutes" | "providers" | "students" | "admin";
48
48
  export type StudentPlacementData = {
49
49
  title: string;
50
50
  reqUserType: "Students" | "Staff" | "Provider" | "Parent";
51
+ reqUserTypes?: ("Students" | "Staff" | "Provider" | "Parent")[];
52
+ /** Set while a completion is claiming the band's single advance, so two
53
+ * parties finishing at once cannot both move the placement on. */
54
+ advancingFrom?: number;
51
55
  lastStageChange?: string;
52
56
  uid: string;
53
57
  startDate: string;
@@ -751,6 +755,7 @@ export type WorkflowStage = {
751
755
  y: number;
752
756
  };
753
757
  userType?: "Students" | "Staff" | "Provider" | "Parent";
758
+ concurrentWithNext?: boolean;
754
759
  forms?: string[];
755
760
  files?: string[];
756
761
  deadline?: string;
@@ -0,0 +1,33 @@
1
+ import { WorkflowStage } from "./typeDefinitions";
2
+ /**
3
+ * Concurrent sign-off bands.
4
+ *
5
+ * Deliberately free of any firebase import so it can be unit tested and pulled
6
+ * into the backend, the external review payload and the staff UI alike.
7
+ */
8
+ /**
9
+ * The stage that starts the concurrent band `stageId` belongs to.
10
+ *
11
+ * A band is a run of stages chained by their first button where every stage but
12
+ * the last carries `concurrentWithNext`. The head is the stage a placement's
13
+ * `status` points at while the band is open, which is what lets `status` stay a
14
+ * single number: the band only changes who we ask, not where the placement is.
15
+ */
16
+ export declare const getBandHead: (workflow: WorkflowStage[], stageId: number) => WorkflowStage | undefined;
17
+ /**
18
+ * Every stage that runs at the same time as `stageId`, in workflow order.
19
+ *
20
+ * Returns just the stage itself when nothing is marked concurrent, so callers can
21
+ * use this unconditionally and existing workflows behave exactly as before.
22
+ */
23
+ export declare const getConcurrentBand: (workflow: WorkflowStage[], stageId: number) => WorkflowStage[];
24
+ /** True when the workflow asks more than one party at this point. */
25
+ export declare const isConcurrentBand: (workflow: WorkflowStage[], stageId: number) => boolean;
26
+ /** The band's stages that nobody has signed off yet. */
27
+ export declare const getBandOutstanding: (band: WorkflowStage[], completedStages?: number[]) => WorkflowStage[];
28
+ /** Distinct user types the band is still waiting on, in workflow order. */
29
+ export declare const getBandOutstandingUserTypes: (band: WorkflowStage[], completedStages?: number[]) => NonNullable<"Students" | "Staff" | "Provider" | "Parent" | undefined>[];
30
+ /** Where the placement goes once every stage in the band is signed off. */
31
+ export declare const getBandExitStageId: (band: WorkflowStage[]) => number | undefined;
32
+ /** The label to show staff for a band, e.g. "Employer review & parental consent". */
33
+ export declare const getBandName: (band: WorkflowStage[]) => string;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getBandName = exports.getBandExitStageId = exports.getBandOutstandingUserTypes = exports.getBandOutstanding = exports.isConcurrentBand = exports.getConcurrentBand = exports.getBandHead = void 0;
4
+ /**
5
+ * Concurrent sign-off bands.
6
+ *
7
+ * Deliberately free of any firebase import so it can be unit tested and pulled
8
+ * into the backend, the external review payload and the staff UI alike.
9
+ */
10
+ /**
11
+ * The stage that starts the concurrent band `stageId` belongs to.
12
+ *
13
+ * A band is a run of stages chained by their first button where every stage but
14
+ * the last carries `concurrentWithNext`. The head is the stage a placement's
15
+ * `status` points at while the band is open, which is what lets `status` stay a
16
+ * single number: the band only changes who we ask, not where the placement is.
17
+ */
18
+ const getBandHead = (workflow, stageId) => {
19
+ let stage = workflow.find((s) => s.id === stageId);
20
+ if (!stage)
21
+ return undefined;
22
+ const visited = new Set([stage.id]);
23
+ for (;;) {
24
+ const previous = workflow.find((s) => s.concurrentWithNext && s.buttons?.[0]?.id === stage?.id);
25
+ // No flagged predecessor means we are already at the head.
26
+ if (!previous || visited.has(previous.id))
27
+ return stage;
28
+ visited.add(previous.id);
29
+ stage = previous;
30
+ }
31
+ };
32
+ exports.getBandHead = getBandHead;
33
+ /**
34
+ * Every stage that runs at the same time as `stageId`, in workflow order.
35
+ *
36
+ * Returns just the stage itself when nothing is marked concurrent, so callers can
37
+ * use this unconditionally and existing workflows behave exactly as before.
38
+ */
39
+ const getConcurrentBand = (workflow, stageId) => {
40
+ const head = (0, exports.getBandHead)(workflow, stageId);
41
+ if (!head)
42
+ return [];
43
+ const band = [head];
44
+ const visited = new Set([head.id]);
45
+ let stage = head;
46
+ while (stage?.concurrentWithNext) {
47
+ const nextId = stage.buttons?.[0]?.id;
48
+ // A flagged stage with nowhere to go, or a loop, ends the band rather
49
+ // than stranding the caller.
50
+ if (nextId === undefined || visited.has(nextId))
51
+ break;
52
+ stage = workflow.find((s) => s.id === nextId);
53
+ if (!stage)
54
+ break;
55
+ visited.add(stage.id);
56
+ band.push(stage);
57
+ }
58
+ return band;
59
+ };
60
+ exports.getConcurrentBand = getConcurrentBand;
61
+ /** True when the workflow asks more than one party at this point. */
62
+ const isConcurrentBand = (workflow, stageId) => (0, exports.getConcurrentBand)(workflow, stageId).length > 1;
63
+ exports.isConcurrentBand = isConcurrentBand;
64
+ /** The band's stages that nobody has signed off yet. */
65
+ const getBandOutstanding = (band, completedStages = []) => band.filter((stage) => !completedStages.includes(stage.id));
66
+ exports.getBandOutstanding = getBandOutstanding;
67
+ /** Distinct user types the band is still waiting on, in workflow order. */
68
+ const getBandOutstandingUserTypes = (band, completedStages = []) => (0, exports.getBandOutstanding)(band, completedStages)
69
+ .map((stage) => stage.userType)
70
+ .filter((userType, i, all) => Boolean(userType) && all.indexOf(userType) === i);
71
+ exports.getBandOutstandingUserTypes = getBandOutstandingUserTypes;
72
+ /** Where the placement goes once every stage in the band is signed off. */
73
+ const getBandExitStageId = (band) => band[band.length - 1]?.buttons?.[0]?.id;
74
+ exports.getBandExitStageId = getBandExitStageId;
75
+ /** The label to show staff for a band, e.g. "Employer review & parental consent". */
76
+ const getBandName = (band) => band.map((stage) => stage.name).join(" & ");
77
+ exports.getBandName = getBandName;
78
+ //# sourceMappingURL=workflowBands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowBands.js","sourceRoot":"","sources":["../src/workflowBands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const workflowBands_1 = require("./workflowBands");
4
+ describe("Concurrent bands", () => {
5
+ // A typical work experience chain: staff check, then employer review, then
6
+ // parental consent, then the placement start stage.
7
+ const stage = (id, userType, next, concurrentWithNext) => ({
8
+ id,
9
+ name: `Stage ${id}`,
10
+ userType,
11
+ concurrentWithNext,
12
+ buttons: next === undefined ? [] : [{ id: next, name: "Continue", required: true }],
13
+ });
14
+ const sequential = [
15
+ stage(1, "Students", 2),
16
+ stage(2, "Staff", 3),
17
+ stage(3, "Provider", 4),
18
+ stage(4, "Parent", 6),
19
+ stage(6, undefined),
20
+ ];
21
+ // Same workflow, but employer review and parental consent run together.
22
+ const banded = [
23
+ stage(1, "Students", 2),
24
+ stage(2, "Staff", 3),
25
+ stage(3, "Provider", 4, true),
26
+ stage(4, "Parent", 6),
27
+ stage(6, undefined),
28
+ ];
29
+ it("returns just the stage when nothing is marked concurrent", () => {
30
+ expect((0, workflowBands_1.getConcurrentBand)(sequential, 3).map((s) => s.id)).toEqual([3]);
31
+ expect((0, workflowBands_1.getConcurrentBand)(sequential, 4).map((s) => s.id)).toEqual([4]);
32
+ expect((0, workflowBands_1.isConcurrentBand)(sequential, 3)).toBe(false);
33
+ });
34
+ it("resolves the same band from any member", () => {
35
+ expect((0, workflowBands_1.getConcurrentBand)(banded, 3).map((s) => s.id)).toEqual([3, 4]);
36
+ expect((0, workflowBands_1.getConcurrentBand)(banded, 4).map((s) => s.id)).toEqual([3, 4]);
37
+ expect((0, workflowBands_1.isConcurrentBand)(banded, 3)).toBe(true);
38
+ });
39
+ it("resolves a band of three from the middle", () => {
40
+ const three = [
41
+ stage(2, "Staff", 3, true),
42
+ stage(3, "Provider", 4, true),
43
+ stage(4, "Parent", 6),
44
+ stage(6, undefined),
45
+ ];
46
+ expect((0, workflowBands_1.getConcurrentBand)(three, 3).map((s) => s.id)).toEqual([2, 3, 4]);
47
+ expect((0, workflowBands_1.getBandHead)(three, 4)?.id).toEqual(2);
48
+ });
49
+ it("does not pull in the stage after the band", () => {
50
+ expect((0, workflowBands_1.getConcurrentBand)(banded, 3).some((s) => s.id === 6)).toBe(false);
51
+ expect((0, workflowBands_1.getBandExitStageId)((0, workflowBands_1.getConcurrentBand)(banded, 3))).toEqual(6);
52
+ });
53
+ it("terminates on a cycle", () => {
54
+ // The builder stops a school authoring this, but a hand-edited workflow
55
+ // must not hang or repeat a stage. A cycle has no real head, so only the
56
+ // membership is meaningful here, not the order.
57
+ const looped = [
58
+ stage(3, "Provider", 4, true),
59
+ stage(4, "Parent", 3, true),
60
+ ];
61
+ const ids = (0, workflowBands_1.getConcurrentBand)(looped, 3).map((s) => s.id);
62
+ expect(ids.slice().sort()).toEqual([3, 4]);
63
+ expect(new Set(ids).size).toEqual(ids.length);
64
+ });
65
+ it("terminates when a flagged stage points nowhere", () => {
66
+ const dangling = [stage(3, "Provider", undefined, true)];
67
+ expect((0, workflowBands_1.getConcurrentBand)(dangling, 3).map((s) => s.id)).toEqual([3]);
68
+ });
69
+ it("returns nothing for a stage that is not in the workflow", () => {
70
+ expect((0, workflowBands_1.getConcurrentBand)(banded, 99)).toEqual([]);
71
+ });
72
+ it("reports what the band is still waiting on", () => {
73
+ const band = (0, workflowBands_1.getConcurrentBand)(banded, 3);
74
+ expect((0, workflowBands_1.getBandOutstanding)(band, []).map((s) => s.id)).toEqual([3, 4]);
75
+ expect((0, workflowBands_1.getBandOutstandingUserTypes)(band, [])).toEqual(["Provider", "Parent"]);
76
+ // The employer has signed off, so only the parent is still owed.
77
+ expect((0, workflowBands_1.getBandOutstanding)(band, [3]).map((s) => s.id)).toEqual([4]);
78
+ expect((0, workflowBands_1.getBandOutstandingUserTypes)(band, [3])).toEqual(["Parent"]);
79
+ expect((0, workflowBands_1.getBandOutstanding)(band, [3, 4])).toEqual([]);
80
+ expect((0, workflowBands_1.getBandOutstandingUserTypes)(band, [3, 4])).toEqual([]);
81
+ });
82
+ it("does not repeat a user type that owns two stages in the band", () => {
83
+ const twoProviderStages = [
84
+ stage(3, "Provider", 4, true),
85
+ stage(4, "Provider", 5, true),
86
+ stage(5, "Parent", 6),
87
+ stage(6, undefined),
88
+ ];
89
+ const band = (0, workflowBands_1.getConcurrentBand)(twoProviderStages, 3);
90
+ expect((0, workflowBands_1.getBandOutstandingUserTypes)(band, [])).toEqual(["Provider", "Parent"]);
91
+ });
92
+ it("names the band for staff", () => {
93
+ expect((0, workflowBands_1.getBandName)((0, workflowBands_1.getConcurrentBand)(banded, 3))).toEqual("Stage 3 & Stage 4");
94
+ expect((0, workflowBands_1.getBandName)((0, workflowBands_1.getConcurrentBand)(sequential, 3))).toEqual("Stage 3");
95
+ });
96
+ });
97
+ //# sourceMappingURL=workflowBands.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowBands.test.js","sourceRoot":"","sources":["../src/workflowBands.test.ts"],"names":[],"mappings":";;AAAA,mDAC6F;AAG7F,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAC9B,2EAA2E;IAC3E,oDAAoD;IACpD,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,QAAmC,EAAE,IAAa,EAAE,kBAA4B,EAAiB,EAAE,CAAC,CAAC;QAC5H,EAAE;QACF,IAAI,EAAE,SAAS,EAAE,EAAE;QACnB,QAAQ;QACR,kBAAkB;QAClB,OAAO,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAC,CAAC;KACpF,CAAC,CAAC;IAEH,MAAM,UAAU,GAAoB;QAChC,KAAK,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC;QACvB,KAAK,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QACpB,KAAK,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC;QACvB,KAAK,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QACrB,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC;KACtB,CAAC;IAEF,wEAAwE;IACxE,MAAM,MAAM,GAAoB;QAC5B,KAAK,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC;QACvB,KAAK,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QACpB,KAAK,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,CAAC;QAC7B,KAAK,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QACrB,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC;KACtB,CAAC;IAEF,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAChE,MAAM,CAAC,IAAA,iCAAiB,EAAC,UAAU,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvE,MAAM,CAAC,IAAA,iCAAiB,EAAC,UAAU,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvE,MAAM,CAAC,IAAA,gCAAgB,EAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,IAAA,iCAAiB,EAAC,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACtE,MAAM,CAAC,IAAA,iCAAiB,EAAC,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACtE,MAAM,CAAC,IAAA,gCAAgB,EAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAChD,MAAM,KAAK,GAAoB;YAC3B,KAAK,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,CAAC;YAC1B,KAAK,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,CAAC;YAC7B,KAAK,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;YACrB,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC;SACtB,CAAC;QACF,MAAM,CAAC,IAAA,iCAAiB,EAAC,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACxE,MAAM,CAAC,IAAA,2BAAW,EAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,IAAA,iCAAiB,EAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzE,MAAM,CAAC,IAAA,kCAAkB,EAAC,IAAA,iCAAiB,EAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC7B,wEAAwE;QACxE,yEAAyE;QACzE,gDAAgD;QAChD,MAAM,MAAM,GAAoB;YAC5B,KAAK,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,CAAC;YAC7B,KAAK,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,CAAC;SAC9B,CAAC;QACF,MAAM,GAAG,GAAG,IAAA,iCAAiB,EAAC,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC1D,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACtD,MAAM,QAAQ,GAAoB,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;QAC1E,MAAM,CAAC,IAAA,iCAAiB,EAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QAC/D,MAAM,CAAC,IAAA,iCAAiB,EAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACjD,MAAM,IAAI,GAAG,IAAA,iCAAiB,EAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,IAAA,kCAAkB,EAAC,IAAI,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACtE,MAAM,CAAC,IAAA,2CAA2B,EAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;QAE9E,iEAAiE;QACjE,MAAM,CAAC,IAAA,kCAAkB,EAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACpE,MAAM,CAAC,IAAA,2CAA2B,EAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QAEnE,MAAM,CAAC,IAAA,kCAAkB,EAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACrD,MAAM,CAAC,IAAA,2CAA2B,EAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACpE,MAAM,iBAAiB,GAAoB;YACvC,KAAK,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,CAAC;YAC7B,KAAK,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,CAAC;YAC7B,KAAK,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;YACrB,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC;SACtB,CAAC;QACF,MAAM,IAAI,GAAG,IAAA,iCAAiB,EAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC;QACrD,MAAM,CAAC,IAAA,2CAA2B,EAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAChC,MAAM,CAAC,IAAA,2BAAW,EAAC,IAAA,iCAAiB,EAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;QAC/E,MAAM,CAAC,IAAA,2BAAW,EAAC,IAAA,iCAAiB,EAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,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.300.1055",
5
+ "version": "1.300.1057",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
8
8
  "scripts": {
package/src/index.ts CHANGED
@@ -31,6 +31,7 @@ export * from "./tutorialTips";
31
31
  export * from "./constants";
32
32
  export * from "./dataViewerReload";
33
33
  export * from "./hooks";
34
+ export * from "./workflowBands";
34
35
  export * from "./util";
35
36
  export * from "./images/GatsbyBenchmarks";
36
37
  export * from "./databaseTypes";
@@ -1,4 +1,4 @@
1
- import {calculateHubPrice, calculateSchoolPrice, DEFAULT_PRICING_CONFIG, InstituteQuoteInput, instituteQuote, mergePricingConfig, quoteSnapshot, SchoolPricingInput} from "./pricing";
1
+ import {calculateHubPrice, calculateSchoolPrice, DEFAULT_PRICING_CONFIG, InstituteQuoteInput, instituteQuote, mergePricingConfig, quoteSnapshot, SchoolPricingInput, trustDiscountRate} from "./pricing";
2
2
 
3
3
  /**
4
4
  * The worked examples from the 2026 pricing sheet, kept here as the contract:
@@ -146,6 +146,53 @@ describe("mergePricingConfig", () => {
146
146
  });
147
147
  });
148
148
 
149
+ describe("the trust discount", () => {
150
+ const trust = (schools: number): SchoolPricingInput =>
151
+ ({students: 10000, events: true, destinations: true, schools});
152
+
153
+ it("gives a trust under ten schools 5% off the whole quote", () => {
154
+ const result = calculateSchoolPrice(trust(9));
155
+ const plain = calculateSchoolPrice({...trust(9), schools: undefined});
156
+
157
+ expect(result.trustDiscountRate).toBe(0.05);
158
+ expect(result.total).toBeCloseTo(plain.total * 0.95, 6);
159
+ expect(result.trustDiscountApplied).toBeCloseTo(plain.total * 0.05, 6);
160
+ });
161
+
162
+ it("gives a trust of ten schools or more 10%", () => {
163
+ expect(calculateSchoolPrice(trust(10)).trustDiscountRate).toBe(0.1);
164
+ expect(calculateSchoolPrice(trust(40)).trustDiscountRate).toBe(0.1);
165
+ });
166
+
167
+ it("gives a single school nothing, however it is counted", () => {
168
+ expect(trustDiscountRate(undefined)).toBe(0);
169
+ expect(trustDiscountRate(0)).toBe(0);
170
+ expect(trustDiscountRate(1)).toBe(0);
171
+ });
172
+
173
+ it("is taken after the full-platform discount and before the negotiated one", () => {
174
+ const input: SchoolPricingInput = {
175
+ students: 10000, wexY10: 500, events: true, destinations: true,
176
+ fullPlatformDiscount: true, schools: 12, bespokeDiscount: 0.1,
177
+ };
178
+ const result = calculateSchoolPrice(input);
179
+ const expected = result.subtotal * 0.8 * 0.9 * 0.9;
180
+
181
+ expect(result.total).toBeCloseTo(expected, 6);
182
+ expect(result.trustDiscountApplied).toBeCloseTo(result.subtotal * 0.8 * 0.1, 6);
183
+ });
184
+
185
+ it("only reaches an institute that is actually a trust", () => {
186
+ const priced = (isTrust: boolean) => instituteQuote({
187
+ products: {events: true, isTrust},
188
+ students: 10000,
189
+ pricing: {schools: 12},
190
+ }).total;
191
+
192
+ expect(priced(true)).toBeCloseTo(priced(false) * 0.9, 6);
193
+ });
194
+ });
195
+
149
196
  describe("the negotiated terms", () => {
150
197
  it("takes the bespoke discount after the full-platform one", () => {
151
198
  const base: SchoolPricingInput = {students: 2000, events: true, destinations: true, fullPlatformDiscount: true};
package/src/pricing.ts CHANGED
@@ -12,6 +12,10 @@
12
12
  * a partial-product discount — that is how the pricing sheet has it, and it is
13
13
  * why `partialProductDiscount` names those two rather than being one number.
14
14
  *
15
+ * A trust is quoted as one account on its combined roll, and earns a discount
16
+ * on the whole quote for the number of schools it brings on — a smaller one
17
+ * below the threshold, a larger one at or above it.
18
+ *
15
19
  * Every figure lives in `PricingConfig` so it can be stored in Firestore and
16
20
  * adjusted without a deploy. `DEFAULT_PRICING_CONFIG` is the fallback and the
17
21
  * seed for that document.
@@ -42,6 +46,15 @@ export type PricingConfig = {
42
46
  partialProductDiscount: {events: number, destinations: number},
43
47
  /** Taken off the whole quote when a school takes the full platform. */
44
48
  fullPlatformDiscount: number,
49
+ /** Taken off the whole quote for a trust, on how many schools it brings. */
50
+ trust: {
51
+ /** Schools at which the larger discount takes over. */
52
+ threshold: number,
53
+ /** Fraction off a trust below the threshold. */
54
+ discount: number,
55
+ /** Fraction off a trust at or above it. */
56
+ largeDiscount: number,
57
+ },
45
58
  /** Careers hubs are a flat platform fee plus a fee for each school. */
46
59
  hub: {baseFee: number, perSchoolFee: number},
47
60
  };
@@ -57,6 +70,7 @@ export const DEFAULT_PRICING_CONFIG: PricingConfig = {
57
70
  ],
58
71
  partialProductDiscount: {events: 0.2, destinations: 0.2},
59
72
  fullPlatformDiscount: 0.2,
73
+ trust: {threshold: 10, discount: 0.05, largeDiscount: 0.1},
60
74
  hub: {baseFee: 2000, perSchoolFee: 150},
61
75
  };
62
76
 
@@ -78,6 +92,8 @@ export type SchoolPricingInput = {
78
92
  destinationsDiscount?: boolean,
79
93
  /** Discount the whole quote, for a school taking the full platform. */
80
94
  fullPlatformDiscount?: boolean,
95
+ /** Schools in the trust being quoted. Two or more earns a trust discount. */
96
+ schools?: number,
81
97
  /** A negotiated discount off the whole quote, as a fraction (0.1 = 10% off).
82
98
  * Taken after the full-platform discount and before the minimum fee. */
83
99
  bespokeDiscount?: number,
@@ -116,7 +132,11 @@ export type SchoolPricingResult = {
116
132
  subtotal: number,
117
133
  /** £ taken off the subtotal by the full-platform discount. */
118
134
  fullPlatformDiscountApplied: number,
119
- /** £ taken off by the negotiated discount, after the full-platform one. */
135
+ /** £ taken off by the trust discount, after the full-platform one. */
136
+ trustDiscountApplied: number,
137
+ /** The rate that trust discount was taken at, as a fraction. */
138
+ trustDiscountRate: number,
139
+ /** £ taken off by the negotiated discount, after the trust one. */
120
140
  bespokeDiscountApplied: number,
121
141
  /** True when the minimum fee, not the products, set the price. */
122
142
  minFeeApplied: boolean,
@@ -175,6 +195,25 @@ const clampFraction = (value?: number) => Math.min(Math.max(value || 0, 0), 1);
175
195
 
176
196
  const bandsTotal = (bands: PricingBand[]) => sum(bands.map((b) => b.amount));
177
197
 
198
+ /**
199
+ * What a trust comes off its quote for its size.
200
+ *
201
+ * A trust is more than one school, so a count below two is a single school and
202
+ * earns nothing — the discount is for bringing schools with you.
203
+ *
204
+ * @param {number} [schools] schools in the trust.
205
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
206
+ * @return {number} the discount, as a fraction of the quote.
207
+ */
208
+ export function trustDiscountRate(
209
+ schools?: number,
210
+ config: PricingConfig = DEFAULT_PRICING_CONFIG,
211
+ ): number {
212
+ const count = schools || 0;
213
+ if (count < 2) return 0;
214
+ return count >= config.trust.threshold ? config.trust.largeDiscount : config.trust.discount;
215
+ }
216
+
178
217
  /**
179
218
  * Price a school, trust or hub-school quote.
180
219
  *
@@ -218,7 +257,9 @@ export function calculateSchoolPrice(
218
257
 
219
258
  const subtotal = sum(lines.map((l) => l.amount));
220
259
  const discounted = input.fullPlatformDiscount ? subtotal * (1 - config.fullPlatformDiscount) : subtotal;
221
- const negotiated = discounted * (1 - clampFraction(input.bespokeDiscount));
260
+ const trustRate = trustDiscountRate(input.schools, config);
261
+ const trusted = discounted * (1 - trustRate);
262
+ const negotiated = trusted * (1 - clampFraction(input.bespokeDiscount));
222
263
  const floored = Math.max(negotiated, config.minFee);
223
264
  const overridden = input.quoteOverride !== undefined && input.quoteOverride >= 0;
224
265
 
@@ -226,7 +267,9 @@ export function calculateSchoolPrice(
226
267
  lines,
227
268
  subtotal,
228
269
  fullPlatformDiscountApplied: subtotal - discounted,
229
- bespokeDiscountApplied: discounted - negotiated,
270
+ trustDiscountApplied: discounted - trusted,
271
+ trustDiscountRate: trustRate,
272
+ bespokeDiscountApplied: trusted - negotiated,
230
273
  // An agreed price is the price, so neither the floor nor the discounts
231
274
  // are what set it — they are only reported when the calculation stands.
232
275
  minFeeApplied: !overridden && floored > negotiated,
@@ -270,6 +313,7 @@ export function mergePricingConfig(stored?: Partial<PricingConfig>): PricingConf
270
313
  tiers: stored.tiers?.length ? stored.tiers.map((t, i) => ({...(d.tiers[i] || d.tiers[d.tiers.length - 1]), ...t})) : d.tiers,
271
314
  partialProductDiscount: {...d.partialProductDiscount, ...stored.partialProductDiscount},
272
315
  fullPlatformDiscount: stored.fullPlatformDiscount ?? d.fullPlatformDiscount,
316
+ trust: {...d.trust, ...stored.trust},
273
317
  hub: {...d.hub, ...stored.hub},
274
318
  };
275
319
  }
@@ -309,6 +353,8 @@ export type InstitutePricing = {
309
353
  discountReason?: string,
310
354
  /** A price agreed with the customer, which replaces the calculated one. */
311
355
  quoteOverride?: number,
356
+ /** Schools in the trust, which set its discount. Trust accounts only. */
357
+ schools?: number,
312
358
  /** Schools on the hub. Hub accounts only. */
313
359
  hubSchools?: number,
314
360
  /** The quote as last saved, for the customer to see. */
@@ -323,6 +369,9 @@ export type InstituteQuote = {
323
369
  /** The lines added up, before any whole-quote discount. */
324
370
  subtotal: number,
325
371
  fullPlatformDiscountApplied: number,
372
+ trustDiscountApplied: number,
373
+ /** The rate the trust discount was taken at, as a fraction. */
374
+ trustDiscountRate: number,
326
375
  bespokeDiscountApplied: number,
327
376
  minFeeApplied: boolean,
328
377
  overrideApplied: boolean,
@@ -347,9 +396,11 @@ export type InstituteQuoteSnapshot = {
347
396
  /** What the quote needs to know about the institute being priced. */
348
397
  export type InstituteQuoteInput = {
349
398
  /** What the institute has, as resolved by `resolveProducts`. */
350
- products: {wex?: boolean, events?: boolean, destinations?: boolean, isHub?: boolean},
399
+ products: {wex?: boolean, events?: boolean, destinations?: boolean, isHub?: boolean, isTrust?: boolean},
351
400
  /** The institute's roll, when the agreed terms don't name one. */
352
401
  students?: number,
402
+ /** Schools in the trust, when the agreed terms don't name a number. */
403
+ schools?: number,
353
404
  /** Schools on the hub, when the agreed terms don't name a number. */
354
405
  hubSchools?: number,
355
406
  /** The terms agreed with this customer. */
@@ -410,6 +461,9 @@ export function hubQuote(
410
461
  lines,
411
462
  subtotal: hub.total,
412
463
  fullPlatformDiscountApplied: 0,
464
+ // A hub is already priced on its schools, so it has no trust discount.
465
+ trustDiscountApplied: 0,
466
+ trustDiscountRate: 0,
413
467
  bespokeDiscountApplied: hub.total - negotiated,
414
468
  minFeeApplied: false,
415
469
  overrideApplied: overridden,
@@ -422,7 +476,8 @@ export function hubQuote(
422
476
  *
423
477
  * The products come from `resolveProducts` rather than the raw flags, so a trust
424
478
  * is priced on the union of its schools' products the same way it is gated on
425
- * them. A year group with no placements agreed is a year group not on work
479
+ * them, and it is a trust — not any school with a number against it — that earns
480
+ * the trust discount. A year group with no placements agreed is a year group not on work
426
481
  * experience — except where the institute has work experience and neither year
427
482
  * group has been filled in, which is a quote waiting to be finished rather than
428
483
  * a school not taking the product, so Y10 is charged at zero to say so.
@@ -446,6 +501,9 @@ export function instituteQuote(
446
501
 
447
502
  return schoolQuote({
448
503
  students: pricing?.students ?? input.students,
504
+ // Only a trust is quoted on a school count, so a single school can never
505
+ // pick up the discount by having a stray number agreed against it.
506
+ schools: input.products.isTrust ? (pricing?.schools ?? input.schools) : undefined,
449
507
  wexY10: wex ? (noYearGroupQuoted ? 0 : pricing?.wexY10) : undefined,
450
508
  wexY12: wex ? pricing?.wexY12 : undefined,
451
509
  events: Boolean(input.products.events),
@@ -36,6 +36,10 @@ export type Products = "institutes"|"providers"|"students"|"admin";
36
36
  export type StudentPlacementData = {
37
37
  title: string,
38
38
  reqUserType: "Students" | "Staff" | "Provider" | "Parent",
39
+ reqUserTypes?: ("Students" | "Staff" | "Provider" | "Parent")[],
40
+ /** Set while a completion is claiming the band's single advance, so two
41
+ * parties finishing at once cannot both move the placement on. */
42
+ advancingFrom?: number,
39
43
  lastStageChange?: string // yyyy-mm-dd Set every time a stage changes!
40
44
  uid: string,
41
45
  startDate: string,
@@ -65,8 +69,6 @@ export type StudentPlacementData = {
65
69
  locality: string,
66
70
  postal_code: string,
67
71
  remote?: boolean;
68
- // The employer contact for this placement is the student's parent or
69
- // guardian, so parent and employer sign-off are the same person.
70
72
  parentIsEmployer?: boolean;
71
73
  what3Words?: string,
72
74
  country: string,
@@ -756,6 +758,7 @@ export type WorkflowStage = {
756
758
  buttons?: {id: number, name: string|false, description?: string, required: boolean}[],
757
759
  pos?: { x: number, y: number },
758
760
  userType?: "Students" | "Staff" | "Provider" | "Parent",
761
+ concurrentWithNext?: boolean,
759
762
  forms?: string[],
760
763
  files?: string[],
761
764
  deadline?: string,
@@ -0,0 +1,111 @@
1
+ import {getBandExitStageId, getBandHead, getBandName, getBandOutstanding,
2
+ getBandOutstandingUserTypes, getConcurrentBand, isConcurrentBand} from "./workflowBands";
3
+ import {WorkflowStage} from "./typeDefinitions";
4
+
5
+ describe("Concurrent bands", () => {
6
+ // A typical work experience chain: staff check, then employer review, then
7
+ // parental consent, then the placement start stage.
8
+ const stage = (id: number, userType: WorkflowStage["userType"], next?: number, concurrentWithNext?: boolean): WorkflowStage => ({
9
+ id,
10
+ name: `Stage ${id}`,
11
+ userType,
12
+ concurrentWithNext,
13
+ buttons: next === undefined ? [] : [{id: next, name: "Continue", required: true}],
14
+ });
15
+
16
+ const sequential: WorkflowStage[] = [
17
+ stage(1, "Students", 2),
18
+ stage(2, "Staff", 3),
19
+ stage(3, "Provider", 4),
20
+ stage(4, "Parent", 6),
21
+ stage(6, undefined),
22
+ ];
23
+
24
+ // Same workflow, but employer review and parental consent run together.
25
+ const banded: WorkflowStage[] = [
26
+ stage(1, "Students", 2),
27
+ stage(2, "Staff", 3),
28
+ stage(3, "Provider", 4, true),
29
+ stage(4, "Parent", 6),
30
+ stage(6, undefined),
31
+ ];
32
+
33
+ it("returns just the stage when nothing is marked concurrent", () => {
34
+ expect(getConcurrentBand(sequential, 3).map((s) => s.id)).toEqual([3]);
35
+ expect(getConcurrentBand(sequential, 4).map((s) => s.id)).toEqual([4]);
36
+ expect(isConcurrentBand(sequential, 3)).toBe(false);
37
+ });
38
+
39
+ it("resolves the same band from any member", () => {
40
+ expect(getConcurrentBand(banded, 3).map((s) => s.id)).toEqual([3, 4]);
41
+ expect(getConcurrentBand(banded, 4).map((s) => s.id)).toEqual([3, 4]);
42
+ expect(isConcurrentBand(banded, 3)).toBe(true);
43
+ });
44
+
45
+ it("resolves a band of three from the middle", () => {
46
+ const three: WorkflowStage[] = [
47
+ stage(2, "Staff", 3, true),
48
+ stage(3, "Provider", 4, true),
49
+ stage(4, "Parent", 6),
50
+ stage(6, undefined),
51
+ ];
52
+ expect(getConcurrentBand(three, 3).map((s) => s.id)).toEqual([2, 3, 4]);
53
+ expect(getBandHead(three, 4)?.id).toEqual(2);
54
+ });
55
+
56
+ it("does not pull in the stage after the band", () => {
57
+ expect(getConcurrentBand(banded, 3).some((s) => s.id === 6)).toBe(false);
58
+ expect(getBandExitStageId(getConcurrentBand(banded, 3))).toEqual(6);
59
+ });
60
+
61
+ it("terminates on a cycle", () => {
62
+ // The builder stops a school authoring this, but a hand-edited workflow
63
+ // must not hang or repeat a stage. A cycle has no real head, so only the
64
+ // membership is meaningful here, not the order.
65
+ const looped: WorkflowStage[] = [
66
+ stage(3, "Provider", 4, true),
67
+ stage(4, "Parent", 3, true),
68
+ ];
69
+ const ids = getConcurrentBand(looped, 3).map((s) => s.id);
70
+ expect(ids.slice().sort()).toEqual([3, 4]);
71
+ expect(new Set(ids).size).toEqual(ids.length);
72
+ });
73
+
74
+ it("terminates when a flagged stage points nowhere", () => {
75
+ const dangling: WorkflowStage[] = [stage(3, "Provider", undefined, true)];
76
+ expect(getConcurrentBand(dangling, 3).map((s) => s.id)).toEqual([3]);
77
+ });
78
+
79
+ it("returns nothing for a stage that is not in the workflow", () => {
80
+ expect(getConcurrentBand(banded, 99)).toEqual([]);
81
+ });
82
+
83
+ it("reports what the band is still waiting on", () => {
84
+ const band = getConcurrentBand(banded, 3);
85
+ expect(getBandOutstanding(band, []).map((s) => s.id)).toEqual([3, 4]);
86
+ expect(getBandOutstandingUserTypes(band, [])).toEqual(["Provider", "Parent"]);
87
+
88
+ // The employer has signed off, so only the parent is still owed.
89
+ expect(getBandOutstanding(band, [3]).map((s) => s.id)).toEqual([4]);
90
+ expect(getBandOutstandingUserTypes(band, [3])).toEqual(["Parent"]);
91
+
92
+ expect(getBandOutstanding(band, [3, 4])).toEqual([]);
93
+ expect(getBandOutstandingUserTypes(band, [3, 4])).toEqual([]);
94
+ });
95
+
96
+ it("does not repeat a user type that owns two stages in the band", () => {
97
+ const twoProviderStages: WorkflowStage[] = [
98
+ stage(3, "Provider", 4, true),
99
+ stage(4, "Provider", 5, true),
100
+ stage(5, "Parent", 6),
101
+ stage(6, undefined),
102
+ ];
103
+ const band = getConcurrentBand(twoProviderStages, 3);
104
+ expect(getBandOutstandingUserTypes(band, [])).toEqual(["Provider", "Parent"]);
105
+ });
106
+
107
+ it("names the band for staff", () => {
108
+ expect(getBandName(getConcurrentBand(banded, 3))).toEqual("Stage 3 & Stage 4");
109
+ expect(getBandName(getConcurrentBand(sequential, 3))).toEqual("Stage 3");
110
+ });
111
+ });
@@ -0,0 +1,82 @@
1
+ import {WorkflowStage} from "./typeDefinitions";
2
+
3
+ /**
4
+ * Concurrent sign-off bands.
5
+ *
6
+ * Deliberately free of any firebase import so it can be unit tested and pulled
7
+ * into the backend, the external review payload and the staff UI alike.
8
+ */
9
+
10
+ /**
11
+ * The stage that starts the concurrent band `stageId` belongs to.
12
+ *
13
+ * A band is a run of stages chained by their first button where every stage but
14
+ * the last carries `concurrentWithNext`. The head is the stage a placement's
15
+ * `status` points at while the band is open, which is what lets `status` stay a
16
+ * single number: the band only changes who we ask, not where the placement is.
17
+ */
18
+ export const getBandHead = (workflow: WorkflowStage[], stageId: number): WorkflowStage|undefined => {
19
+ let stage = workflow.find((s) => s.id === stageId);
20
+ if (!stage) return undefined;
21
+
22
+ const visited = new Set<number>([stage.id]);
23
+
24
+ for (;;) {
25
+ const previous = workflow.find((s) => s.concurrentWithNext && s.buttons?.[0]?.id === stage?.id);
26
+ // No flagged predecessor means we are already at the head.
27
+ if (!previous || visited.has(previous.id)) return stage;
28
+ visited.add(previous.id);
29
+ stage = previous;
30
+ }
31
+ };
32
+
33
+ /**
34
+ * Every stage that runs at the same time as `stageId`, in workflow order.
35
+ *
36
+ * Returns just the stage itself when nothing is marked concurrent, so callers can
37
+ * use this unconditionally and existing workflows behave exactly as before.
38
+ */
39
+ export const getConcurrentBand = (workflow: WorkflowStage[], stageId: number): WorkflowStage[] => {
40
+ const head = getBandHead(workflow, stageId);
41
+ if (!head) return [];
42
+
43
+ const band = [head];
44
+ const visited = new Set<number>([head.id]);
45
+
46
+ let stage: WorkflowStage|undefined = head;
47
+ while (stage?.concurrentWithNext) {
48
+ const nextId: number|undefined = stage.buttons?.[0]?.id;
49
+ // A flagged stage with nowhere to go, or a loop, ends the band rather
50
+ // than stranding the caller.
51
+ if (nextId === undefined || visited.has(nextId)) break;
52
+ stage = workflow.find((s) => s.id === nextId);
53
+ if (!stage) break;
54
+ visited.add(stage.id);
55
+ band.push(stage);
56
+ }
57
+
58
+ return band;
59
+ };
60
+
61
+ /** True when the workflow asks more than one party at this point. */
62
+ export const isConcurrentBand = (workflow: WorkflowStage[], stageId: number) =>
63
+ getConcurrentBand(workflow, stageId).length > 1;
64
+
65
+ /** The band's stages that nobody has signed off yet. */
66
+ export const getBandOutstanding = (band: WorkflowStage[], completedStages: number[] = []) =>
67
+ band.filter((stage) => !completedStages.includes(stage.id));
68
+
69
+ /** Distinct user types the band is still waiting on, in workflow order. */
70
+ export const getBandOutstandingUserTypes = (band: WorkflowStage[], completedStages: number[] = []) =>
71
+ getBandOutstanding(band, completedStages)
72
+ .map((stage) => stage.userType)
73
+ .filter((userType, i, all): userType is NonNullable<WorkflowStage["userType"]> =>
74
+ Boolean(userType) && all.indexOf(userType) === i);
75
+
76
+ /** Where the placement goes once every stage in the band is signed off. */
77
+ export const getBandExitStageId = (band: WorkflowStage[]) =>
78
+ band[band.length - 1]?.buttons?.[0]?.id;
79
+
80
+ /** The label to show staff for a band, e.g. "Employer review & parental consent". */
81
+ export const getBandName = (band: WorkflowStage[]) =>
82
+ band.map((stage) => stage.name).join(" & ");