placementt-core 1.400.1071 → 1.400.1072

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/pricing.d.ts CHANGED
@@ -1,24 +1,62 @@
1
+ /**
2
+ * The 2026 pricing model.
3
+ *
4
+ * A quote is a base fee plus a per-product fee, and every per-product fee is
5
+ * graduated: the tiers are bands, not lookups, so a school with 1,250 students
6
+ * pays the tier 1 rate on its first 1,000 and the tier 2 rate on the next 250.
7
+ * Anything above the final threshold keeps paying the final tier's rate.
8
+ *
9
+ * Work experience is charged per placement (Y10 and Y12 are quoted separately,
10
+ * and a year group left blank is a year group not taking the product); events
11
+ * and destinations are charged per student. Only events and destinations carry
12
+ * a partial-product discount — that is how the pricing sheet has it, and it is
13
+ * why `partialProductDiscount` names those two rather than being one number.
14
+ *
15
+ * A trust is priced as one account on the average school terms, multiplied by
16
+ * the number of schools it brings on, and then earns a discount on the whole
17
+ * quote — a smaller one below the threshold, a larger one at or above it.
18
+ *
19
+ * Every figure lives in `PricingConfig` so it can be stored in Firestore and
20
+ * adjusted without a deploy. `DEFAULT_PRICING_CONFIG` is the fallback and the
21
+ * seed for that document.
22
+ */
23
+ /** One band of the graduated fee, with the rate each product charges in it. */
1
24
  export type PricingTier = {
25
+ /** Upper bound of the band, in students (events and destinations). */
2
26
  students: number;
27
+ /** Upper bound of the band, in placements (work experience). */
3
28
  placements: number;
29
+ /** £ per placement. */
4
30
  wex: number;
31
+ /** £ per student. */
5
32
  events: number;
33
+ /** £ per student. */
6
34
  destinations: number;
7
35
  };
8
36
  export type PricingConfig = {
37
+ /** No school is quoted below this, however little the products come to. */
9
38
  minFee: number;
39
+ /** Charged on every quote. Not itemised to the customer. */
10
40
  baseFee: number;
41
+ /** In ascending order of threshold. The last band's rate runs to infinity. */
11
42
  tiers: PricingTier[];
43
+ /** Taken off a single product's fee, as a fraction (0.2 = 20% off). */
12
44
  partialProductDiscount: {
13
45
  events: number;
14
46
  destinations: number;
15
47
  };
48
+ /** Taken off the whole quote when a school takes the full platform. */
16
49
  fullPlatformDiscount: number;
50
+ /** Taken off the whole quote for a trust, on how many schools it brings. */
17
51
  trust: {
52
+ /** Schools at which the larger discount takes over. */
18
53
  threshold: number;
54
+ /** Fraction off a trust below the threshold. */
19
55
  discount: number;
56
+ /** Fraction off a trust at or above it. */
20
57
  largeDiscount: number;
21
58
  };
59
+ /** Careers hubs are a flat platform fee plus a fee for each school. */
22
60
  hub: {
23
61
  baseFee: number;
24
62
  perSchoolFee: number;
@@ -26,93 +64,182 @@ export type PricingConfig = {
26
64
  };
27
65
  /** The pricing sheet's figures, as agreed for 2026. */
28
66
  export declare const DEFAULT_PRICING_CONFIG: PricingConfig;
67
+ /** Where the adjustable copy of the config lives. */
29
68
  export declare const PRICING_CONFIG_PATH: string[];
30
69
  export type SchoolPricingInput = {
70
+ /** Roll size. Drives the events and destinations fees. */
31
71
  students?: number;
72
+ /** Y10 placements. Undefined means the year group isn't taking work experience. */
32
73
  wexY10?: number;
74
+ /** Y12 placements. Undefined means the year group isn't taking work experience. */
33
75
  wexY12?: number;
34
76
  events?: boolean;
77
+ /** Discount the events fee on its own. */
35
78
  eventsDiscount?: boolean;
36
79
  destinations?: boolean;
80
+ /** Discount the destinations fee on its own. */
37
81
  destinationsDiscount?: boolean;
82
+ /** Discount the whole quote, for a school taking the full platform. */
38
83
  fullPlatformDiscount?: boolean;
84
+ /** Schools in the trust being quoted. When a trust is quoted, its school-level
85
+ * terms are multiplied by this count before any trust discount is taken. */
39
86
  schools?: number;
87
+ /** A negotiated discount off the whole quote, as a fraction (0.1 = 10% off).
88
+ * Taken after the full-platform discount and before the minimum fee. */
40
89
  bespokeDiscount?: number;
90
+ /** A price agreed with the customer, which replaces everything above it —
91
+ * the discounts, the minimum fee and the total the products came to. */
41
92
  quoteOverride?: number;
42
93
  };
43
94
  export type PricingLineKey = "base" | "wexY10" | "wexY12" | "events" | "destinations" | "hubBase" | "hubSchools";
44
95
  export type PricingLine = {
45
96
  key: PricingLineKey;
46
97
  label: string;
98
+ /** What the product costs after any partial-product discount. */
47
99
  amount: number;
100
+ /** The fee before its partial-product discount, when one was applied. */
48
101
  undiscounted?: number;
102
+ /** How the amount was reached, band by band. Empty for the base fee. */
49
103
  bands: PricingBand[];
50
104
  };
105
+ /** One band's contribution to a product's fee, for showing the working. */
51
106
  export type PricingBand = {
107
+ /** 1-indexed, matching the tier's position in the config. */
52
108
  tier: number;
109
+ /** Units charged in this band. */
53
110
  quantity: number;
54
111
  rate: number;
55
112
  amount: number;
56
113
  };
57
114
  export type SchoolPricingResult = {
58
115
  lines: PricingLine[];
116
+ /** The lines added up, before the full-platform discount. */
59
117
  subtotal: number;
118
+ /** £ taken off the subtotal by the full-platform discount. */
60
119
  fullPlatformDiscountApplied: number;
120
+ /** £ taken off by the trust discount, after the full-platform one. */
61
121
  trustDiscountApplied: number;
122
+ /** The rate that trust discount was taken at, as a fraction. */
62
123
  trustDiscountRate: number;
124
+ /** £ taken off by the negotiated discount, after the trust one. */
63
125
  bespokeDiscountApplied: number;
126
+ /** True when the minimum fee, not the products, set the price. */
64
127
  minFeeApplied: boolean;
128
+ /** True when an agreed price replaced the calculated one. */
65
129
  overrideApplied: boolean;
66
130
  total: number;
67
131
  };
68
132
  export type HubPricingResult = {
69
133
  baseFee: number;
134
+ /** The per-school fee multiplied by the number of schools. */
70
135
  schoolFees: number;
71
136
  total: number;
72
137
  };
138
+ /**
139
+ * What a trust comes off its quote for its size.
140
+ *
141
+ * A trust is more than one school, so a count below two is a single school and
142
+ * earns nothing — the discount is for bringing schools with you.
143
+ *
144
+ * @param {number} [schools] schools in the trust.
145
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
146
+ * @return {number} the discount, as a fraction of the quote.
147
+ */
73
148
  export declare function trustDiscountRate(schools?: number, config?: PricingConfig): number;
149
+ /**
150
+ * Price a school, trust or hub-school quote.
151
+ *
152
+ * Products the school isn't taking are left out of `lines` entirely, so the
153
+ * breakdown only ever shows what is being charged for.
154
+ *
155
+ * @param {object} input the roll, the year groups and the products taken.
156
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
157
+ * @return {object} the total, and the working that reached it.
158
+ */
74
159
  export declare function calculateSchoolPrice(input: SchoolPricingInput, config?: PricingConfig): SchoolPricingResult;
160
+ /**
161
+ * Price a careers hub: the platform fee, plus a fee for every school on it.
162
+ *
163
+ * @param {number} schools how many schools the hub brings on.
164
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
165
+ * @return {object} the platform fee, the school fees and the total.
166
+ */
75
167
  export declare function calculateHubPrice(schools: number, config?: PricingConfig): HubPricingResult;
168
+ /**
169
+ * Fill a stored config out with the defaults.
170
+ *
171
+ * The Firestore document is hand-edited, so treat every branch of it as
172
+ * possibly absent — a config saved before a tier was added should still price,
173
+ * rather than throwing on a missing rate.
174
+ *
175
+ * @param {object} [stored] whatever the Firestore document holds.
176
+ * @return {object} a complete config, safe to price with.
177
+ */
76
178
  export declare function mergePricingConfig(stored?: Partial<PricingConfig>): PricingConfig;
179
+ /** The terms agreed with one customer. Stored at `institutes/{oId}.pricing`. */
77
180
  export type InstitutePricing = {
181
+ /** The roll we quoted on. Defaults to the institute's own student count. */
78
182
  students?: number;
183
+ /** Y10 placements quoted. Undefined means the year group isn't on work experience. */
79
184
  wexY10?: number;
185
+ /** Y12 placements quoted. Undefined means the year group isn't on work experience. */
80
186
  wexY12?: number;
187
+ /** Take the partial-product discount off the events fee. */
81
188
  eventsDiscount?: boolean;
189
+ /** Take the partial-product discount off the destinations fee. */
82
190
  destinationsDiscount?: boolean;
191
+ /** Take the full-platform discount off the whole quote. */
83
192
  fullPlatformDiscount?: boolean;
193
+ /** A negotiated discount off the whole quote, as a fraction (0.1 = 10% off). */
84
194
  bespokeDiscount?: number;
195
+ /** Why the negotiated discount or the agreed price was given. Admin-only. */
85
196
  discountReason?: string;
197
+ /** A price agreed with the customer, which replaces the calculated one. */
86
198
  quoteOverride?: number;
199
+ /** Schools in the trust, which set its discount. Trust accounts only. */
87
200
  schools?: number;
201
+ /** Schools on the hub. Hub accounts only. */
88
202
  hubSchools?: number;
203
+ /** The quote as last saved, for the customer to see. */
89
204
  quote?: InstituteQuoteSnapshot;
90
205
  };
206
+ /** A priced institute: the same shape for a school, a trust and a hub. */
91
207
  export type InstituteQuote = {
92
208
  kind: "school" | "hub";
209
+ /** Every fee charged, with the working behind it. */
93
210
  lines: PricingLine[];
211
+ /** The lines added up, before any whole-quote discount. */
94
212
  subtotal: number;
95
213
  fullPlatformDiscountApplied: number;
96
214
  trustDiscountApplied: number;
215
+ /** The rate the trust discount was taken at, as a fraction. */
97
216
  trustDiscountRate: number;
98
217
  bespokeDiscountApplied: number;
99
218
  minFeeApplied: boolean;
100
219
  overrideApplied: boolean;
101
220
  total: number;
102
221
  };
222
+ /** What the customer is shown: the total, and what makes it up. No rates. */
103
223
  export type InstituteQuoteSnapshot = {
104
224
  lines: {
105
225
  key: PricingLineKey;
106
226
  label: string;
107
227
  amount: number;
108
228
  }[];
229
+ /** The lines added up. */
109
230
  subtotal: number;
231
+ /** Everything taken off the lines to reach the total, as one figure. */
110
232
  discountTotal: number;
233
+ /** True when the total is a floor or an agreed price above what the lines
234
+ * come to, so the breakdown is shown as an illustration, not a sum. */
111
235
  minimum: boolean;
112
236
  total: number;
237
+ /** ISO date the quote was last worked out. */
113
238
  updated: string;
114
239
  };
240
+ /** What the quote needs to know about the institute being priced. */
115
241
  export type InstituteQuoteInput = {
242
+ /** What the institute has, as resolved by `resolveProducts`. */
116
243
  products: {
117
244
  wex?: boolean;
118
245
  events?: boolean;
@@ -120,12 +247,60 @@ export type InstituteQuoteInput = {
120
247
  isHub?: boolean;
121
248
  isTrust?: boolean;
122
249
  };
250
+ /** The institute's roll, when the agreed terms don't name one. */
123
251
  students?: number;
252
+ /** Schools in the trust, when the agreed terms don't name a number. */
124
253
  schools?: number;
254
+ /** Schools on the hub, when the agreed terms don't name a number. */
125
255
  hubSchools?: number;
256
+ /** The terms agreed with this customer. */
126
257
  pricing?: InstitutePricing;
127
258
  };
259
+ /**
260
+ * Price a school and present it as an institute quote.
261
+ *
262
+ * @param {object} input the roll, the year groups, the products and the terms.
263
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
264
+ * @return {object} the quote, ready to render.
265
+ */
128
266
  export declare function schoolQuote(input: SchoolPricingInput, config?: PricingConfig): InstituteQuote;
267
+ /**
268
+ * Price a careers hub as an institute quote: the platform fee, then the schools.
269
+ *
270
+ * Hubs have no products to discount individually and no minimum fee to fall back
271
+ * on, so the only terms that reach them are the negotiated discount and an
272
+ * agreed price.
273
+ *
274
+ * @param {number} schools how many schools the hub brings on.
275
+ * @param {object} [pricing] the terms agreed with this hub.
276
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
277
+ * @return {object} the quote, ready to render.
278
+ */
129
279
  export declare function hubQuote(schools: number, pricing?: InstitutePricing, config?: PricingConfig): InstituteQuote;
280
+ /**
281
+ * Price an institute on what it actually has and what was agreed with it.
282
+ *
283
+ * The products come from `resolveProducts` rather than the raw flags, so a trust
284
+ * is priced on the union of its schools' products the same way it is gated on
285
+ * them, and it is a trust — not any school with a number against it — that earns
286
+ * the trust discount. A year group with no placements agreed is a year group not on work
287
+ * experience — except where the institute has work experience and neither year
288
+ * group has been filled in, which is a quote waiting to be finished rather than
289
+ * a school not taking the product, so Y10 is charged at zero to say so.
290
+ *
291
+ * @param {object} input the institute's products, roll and agreed terms.
292
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
293
+ * @return {object} the quote, ready to render.
294
+ */
130
295
  export declare function instituteQuote(input: InstituteQuoteInput, config?: PricingConfig): InstituteQuote;
296
+ /**
297
+ * Reduce a quote to what the customer sees: the fees, and one discount figure.
298
+ *
299
+ * The base fee isn't itemised to customers, so it is folded into the first fee
300
+ * they are shown — and where there is nothing to fold it into, it stands alone
301
+ * as the platform fee it is.
302
+ *
303
+ * @param {object} quote the priced institute.
304
+ * @return {object} the snapshot to store on the institute.
305
+ */
131
306
  export declare function quoteSnapshot(quote: InstituteQuote): InstituteQuoteSnapshot;
package/lib/pricing.js CHANGED
@@ -1,4 +1,26 @@
1
1
  "use strict";
2
+ /**
3
+ * The 2026 pricing model.
4
+ *
5
+ * A quote is a base fee plus a per-product fee, and every per-product fee is
6
+ * graduated: the tiers are bands, not lookups, so a school with 1,250 students
7
+ * pays the tier 1 rate on its first 1,000 and the tier 2 rate on the next 250.
8
+ * Anything above the final threshold keeps paying the final tier's rate.
9
+ *
10
+ * Work experience is charged per placement (Y10 and Y12 are quoted separately,
11
+ * and a year group left blank is a year group not taking the product); events
12
+ * and destinations are charged per student. Only events and destinations carry
13
+ * a partial-product discount — that is how the pricing sheet has it, and it is
14
+ * why `partialProductDiscount` names those two rather than being one number.
15
+ *
16
+ * A trust is priced as one account on the average school terms, multiplied by
17
+ * the number of schools it brings on, and then earns a discount on the whole
18
+ * quote — a smaller one below the threshold, a larger one at or above it.
19
+ *
20
+ * Every figure lives in `PricingConfig` so it can be stored in Firestore and
21
+ * adjusted without a deploy. `DEFAULT_PRICING_CONFIG` is the fallback and the
22
+ * seed for that document.
23
+ */
2
24
  Object.defineProperty(exports, "__esModule", { value: true });
3
25
  exports.PRICING_CONFIG_PATH = exports.DEFAULT_PRICING_CONFIG = void 0;
4
26
  exports.trustDiscountRate = trustDiscountRate;
@@ -23,7 +45,16 @@ exports.DEFAULT_PRICING_CONFIG = {
23
45
  trust: { threshold: 10, discount: 0.05, largeDiscount: 0.1 },
24
46
  hub: { baseFee: 2000, perSchoolFee: 150 },
25
47
  };
48
+ /** Where the adjustable copy of the config lives. */
26
49
  exports.PRICING_CONFIG_PATH = ["adminConfig", "pricing"];
50
+ /**
51
+ * Charge `quantity` across the bands, each band at its own rate, with anything
52
+ * above the final threshold staying on the final band's rate.
53
+ *
54
+ * @param {number} quantity students or placements, depending on the product.
55
+ * @param {Array} bands each band's upper threshold and its rate.
56
+ * @return {Array} one entry per band that charged something.
57
+ */
27
58
  function graduated(quantity, bands) {
28
59
  if (!(quantity > 0) || !bands.length)
29
60
  return [];
@@ -35,6 +66,8 @@ function graduated(quantity, bands) {
35
66
  if (inBand > 0)
36
67
  lines.push({ tier: index + 1, quantity: inBand, rate: band.rate, amount: inBand * band.rate });
37
68
  });
69
+ // Everything past the last threshold keeps paying the last rate, so it belongs
70
+ // in the last band rather than as a second row at the same rate.
38
71
  const last = bands[bands.length - 1];
39
72
  const overflow = Math.max(quantity - last.limit, 0);
40
73
  if (overflow > 0) {
@@ -52,12 +85,32 @@ function graduated(quantity, bands) {
52
85
  const sum = (values) => values.reduce((total, value) => total + value, 0);
53
86
  const clampFraction = (value) => Math.min(Math.max(value || 0, 0), 1);
54
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
+ */
55
98
  function trustDiscountRate(schools, config = exports.DEFAULT_PRICING_CONFIG) {
56
99
  const count = schools || 0;
57
100
  if (count < 2)
58
101
  return 0;
59
102
  return count >= config.trust.threshold ? config.trust.largeDiscount : config.trust.discount;
60
103
  }
104
+ /**
105
+ * Price a school, trust or hub-school quote.
106
+ *
107
+ * Products the school isn't taking are left out of `lines` entirely, so the
108
+ * breakdown only ever shows what is being charged for.
109
+ *
110
+ * @param {object} input the roll, the year groups and the products taken.
111
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
112
+ * @return {object} the total, and the working that reached it.
113
+ */
61
114
  function calculateSchoolPrice(input, config = exports.DEFAULT_PRICING_CONFIG) {
62
115
  const schoolCount = Math.max(input.schools || 0, 0);
63
116
  const students = input.students || 0;
@@ -114,10 +167,27 @@ function calculateSchoolPrice(input, config = exports.DEFAULT_PRICING_CONFIG) {
114
167
  total: overridden ? input.quoteOverride : floored,
115
168
  };
116
169
  }
170
+ /**
171
+ * Price a careers hub: the platform fee, plus a fee for every school on it.
172
+ *
173
+ * @param {number} schools how many schools the hub brings on.
174
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
175
+ * @return {object} the platform fee, the school fees and the total.
176
+ */
117
177
  function calculateHubPrice(schools, config = exports.DEFAULT_PRICING_CONFIG) {
118
178
  const schoolFees = Math.max(schools || 0, 0) * config.hub.perSchoolFee;
119
179
  return { baseFee: config.hub.baseFee, schoolFees, total: config.hub.baseFee + schoolFees };
120
180
  }
181
+ /**
182
+ * Fill a stored config out with the defaults.
183
+ *
184
+ * The Firestore document is hand-edited, so treat every branch of it as
185
+ * possibly absent — a config saved before a tier was added should still price,
186
+ * rather than throwing on a missing rate.
187
+ *
188
+ * @param {object} [stored] whatever the Firestore document holds.
189
+ * @return {object} a complete config, safe to price with.
190
+ */
121
191
  function mergePricingConfig(stored) {
122
192
  const d = exports.DEFAULT_PRICING_CONFIG;
123
193
  if (!stored)
@@ -132,9 +202,28 @@ function mergePricingConfig(stored) {
132
202
  hub: { ...d.hub, ...stored.hub },
133
203
  };
134
204
  }
205
+ /**
206
+ * Price a school and present it as an institute quote.
207
+ *
208
+ * @param {object} input the roll, the year groups, the products and the terms.
209
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
210
+ * @return {object} the quote, ready to render.
211
+ */
135
212
  function schoolQuote(input, config = exports.DEFAULT_PRICING_CONFIG) {
136
213
  return { kind: "school", ...calculateSchoolPrice(input, config) };
137
214
  }
215
+ /**
216
+ * Price a careers hub as an institute quote: the platform fee, then the schools.
217
+ *
218
+ * Hubs have no products to discount individually and no minimum fee to fall back
219
+ * on, so the only terms that reach them are the negotiated discount and an
220
+ * agreed price.
221
+ *
222
+ * @param {number} schools how many schools the hub brings on.
223
+ * @param {object} [pricing] the terms agreed with this hub.
224
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
225
+ * @return {object} the quote, ready to render.
226
+ */
138
227
  function hubQuote(schools, pricing, config = exports.DEFAULT_PRICING_CONFIG) {
139
228
  const count = Math.max(schools || 0, 0);
140
229
  const hub = calculateHubPrice(count, config);
@@ -165,6 +254,21 @@ function hubQuote(schools, pricing, config = exports.DEFAULT_PRICING_CONFIG) {
165
254
  total: overridden ? pricing?.quoteOverride : negotiated,
166
255
  };
167
256
  }
257
+ /**
258
+ * Price an institute on what it actually has and what was agreed with it.
259
+ *
260
+ * The products come from `resolveProducts` rather than the raw flags, so a trust
261
+ * is priced on the union of its schools' products the same way it is gated on
262
+ * them, and it is a trust — not any school with a number against it — that earns
263
+ * the trust discount. A year group with no placements agreed is a year group not on work
264
+ * experience — except where the institute has work experience and neither year
265
+ * group has been filled in, which is a quote waiting to be finished rather than
266
+ * a school not taking the product, so Y10 is charged at zero to say so.
267
+ *
268
+ * @param {object} input the institute's products, roll and agreed terms.
269
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
270
+ * @return {object} the quote, ready to render.
271
+ */
168
272
  function instituteQuote(input, config = exports.DEFAULT_PRICING_CONFIG) {
169
273
  const pricing = input.pricing;
170
274
  if (input.products.isHub) {
@@ -188,6 +292,16 @@ function instituteQuote(input, config = exports.DEFAULT_PRICING_CONFIG) {
188
292
  quoteOverride: pricing?.quoteOverride,
189
293
  }, config);
190
294
  }
295
+ /**
296
+ * Reduce a quote to what the customer sees: the fees, and one discount figure.
297
+ *
298
+ * The base fee isn't itemised to customers, so it is folded into the first fee
299
+ * they are shown — and where there is nothing to fold it into, it stands alone
300
+ * as the platform fee it is.
301
+ *
302
+ * @param {object} quote the priced institute.
303
+ * @return {object} the snapshot to store on the institute.
304
+ */
191
305
  function quoteSnapshot(quote) {
192
306
  const itemised = quote.lines.filter((l) => l.key !== "base");
193
307
  const base = quote.lines.find((l) => l.key === "base")?.amount || 0;
@@ -202,6 +316,9 @@ function quoteSnapshot(quote) {
202
316
  return {
203
317
  lines,
204
318
  subtotal,
319
+ // The customer is shown the fees and one discount, so whatever separates
320
+ // the two — the whole-quote discounts, an agreed price below them — is
321
+ // that one figure, and a total above the fees is flagged instead.
205
322
  discountTotal: Math.max(subtotal - quote.total, 0),
206
323
  minimum: quote.total > subtotal,
207
324
  total: quote.total,
@@ -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":"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"}
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.1071",
5
+ "version": "1.400.1072",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
8
8
  "scripts": {
package/src/pricing.ts CHANGED
@@ -1,23 +1,61 @@
1
-
1
+ /**
2
+ * The 2026 pricing model.
3
+ *
4
+ * A quote is a base fee plus a per-product fee, and every per-product fee is
5
+ * graduated: the tiers are bands, not lookups, so a school with 1,250 students
6
+ * pays the tier 1 rate on its first 1,000 and the tier 2 rate on the next 250.
7
+ * Anything above the final threshold keeps paying the final tier's rate.
8
+ *
9
+ * Work experience is charged per placement (Y10 and Y12 are quoted separately,
10
+ * and a year group left blank is a year group not taking the product); events
11
+ * and destinations are charged per student. Only events and destinations carry
12
+ * a partial-product discount — that is how the pricing sheet has it, and it is
13
+ * why `partialProductDiscount` names those two rather than being one number.
14
+ *
15
+ * A trust is priced as one account on the average school terms, multiplied by
16
+ * the number of schools it brings on, and then earns a discount on the whole
17
+ * quote — a smaller one below the threshold, a larger one at or above it.
18
+ *
19
+ * Every figure lives in `PricingConfig` so it can be stored in Firestore and
20
+ * adjusted without a deploy. `DEFAULT_PRICING_CONFIG` is the fallback and the
21
+ * seed for that document.
22
+ */
23
+
24
+ /** One band of the graduated fee, with the rate each product charges in it. */
2
25
  export type PricingTier = {
26
+ /** Upper bound of the band, in students (events and destinations). */
3
27
  students: number,
28
+ /** Upper bound of the band, in placements (work experience). */
4
29
  placements: number,
30
+ /** £ per placement. */
5
31
  wex: number,
32
+ /** £ per student. */
6
33
  events: number,
34
+ /** £ per student. */
7
35
  destinations: number,
8
36
  };
9
37
 
10
38
  export type PricingConfig = {
39
+ /** No school is quoted below this, however little the products come to. */
11
40
  minFee: number,
41
+ /** Charged on every quote. Not itemised to the customer. */
12
42
  baseFee: number,
43
+ /** In ascending order of threshold. The last band's rate runs to infinity. */
13
44
  tiers: PricingTier[],
45
+ /** Taken off a single product's fee, as a fraction (0.2 = 20% off). */
14
46
  partialProductDiscount: {events: number, destinations: number},
47
+ /** Taken off the whole quote when a school takes the full platform. */
15
48
  fullPlatformDiscount: number,
49
+ /** Taken off the whole quote for a trust, on how many schools it brings. */
16
50
  trust: {
51
+ /** Schools at which the larger discount takes over. */
17
52
  threshold: number,
53
+ /** Fraction off a trust below the threshold. */
18
54
  discount: number,
55
+ /** Fraction off a trust at or above it. */
19
56
  largeDiscount: number,
20
57
  },
58
+ /** Careers hubs are a flat platform fee plus a fee for each school. */
21
59
  hub: {baseFee: number, perSchoolFee: number},
22
60
  };
23
61
 
@@ -36,19 +74,32 @@ export const DEFAULT_PRICING_CONFIG: PricingConfig = {
36
74
  hub: {baseFee: 2000, perSchoolFee: 150},
37
75
  };
38
76
 
77
+ /** Where the adjustable copy of the config lives. */
39
78
  export const PRICING_CONFIG_PATH = ["adminConfig", "pricing"];
40
79
 
41
80
  export type SchoolPricingInput = {
81
+ /** Roll size. Drives the events and destinations fees. */
42
82
  students?: number,
83
+ /** Y10 placements. Undefined means the year group isn't taking work experience. */
43
84
  wexY10?: number,
85
+ /** Y12 placements. Undefined means the year group isn't taking work experience. */
44
86
  wexY12?: number,
45
87
  events?: boolean,
88
+ /** Discount the events fee on its own. */
46
89
  eventsDiscount?: boolean,
47
90
  destinations?: boolean,
91
+ /** Discount the destinations fee on its own. */
48
92
  destinationsDiscount?: boolean,
93
+ /** Discount the whole quote, for a school taking the full platform. */
49
94
  fullPlatformDiscount?: boolean,
95
+ /** Schools in the trust being quoted. When a trust is quoted, its school-level
96
+ * terms are multiplied by this count before any trust discount is taken. */
50
97
  schools?: number,
98
+ /** A negotiated discount off the whole quote, as a fraction (0.1 = 10% off).
99
+ * Taken after the full-platform discount and before the minimum fee. */
51
100
  bespokeDiscount?: number,
101
+ /** A price agreed with the customer, which replaces everything above it —
102
+ * the discounts, the minimum fee and the total the products came to. */
52
103
  quoteOverride?: number,
53
104
  };
54
105
 
@@ -58,13 +109,19 @@ export type PricingLineKey =
58
109
  export type PricingLine = {
59
110
  key: PricingLineKey,
60
111
  label: string,
112
+ /** What the product costs after any partial-product discount. */
61
113
  amount: number,
114
+ /** The fee before its partial-product discount, when one was applied. */
62
115
  undiscounted?: number,
116
+ /** How the amount was reached, band by band. Empty for the base fee. */
63
117
  bands: PricingBand[],
64
118
  };
65
119
 
120
+ /** One band's contribution to a product's fee, for showing the working. */
66
121
  export type PricingBand = {
122
+ /** 1-indexed, matching the tier's position in the config. */
67
123
  tier: number,
124
+ /** Units charged in this band. */
68
125
  quantity: number,
69
126
  rate: number,
70
127
  amount: number,
@@ -72,22 +129,38 @@ export type PricingBand = {
72
129
 
73
130
  export type SchoolPricingResult = {
74
131
  lines: PricingLine[],
132
+ /** The lines added up, before the full-platform discount. */
75
133
  subtotal: number,
134
+ /** £ taken off the subtotal by the full-platform discount. */
76
135
  fullPlatformDiscountApplied: number,
136
+ /** £ taken off by the trust discount, after the full-platform one. */
77
137
  trustDiscountApplied: number,
138
+ /** The rate that trust discount was taken at, as a fraction. */
78
139
  trustDiscountRate: number,
140
+ /** £ taken off by the negotiated discount, after the trust one. */
79
141
  bespokeDiscountApplied: number,
142
+ /** True when the minimum fee, not the products, set the price. */
80
143
  minFeeApplied: boolean,
144
+ /** True when an agreed price replaced the calculated one. */
81
145
  overrideApplied: boolean,
82
146
  total: number,
83
147
  };
84
148
 
85
149
  export type HubPricingResult = {
86
150
  baseFee: number,
151
+ /** The per-school fee multiplied by the number of schools. */
87
152
  schoolFees: number,
88
153
  total: number,
89
154
  };
90
155
 
156
+ /**
157
+ * Charge `quantity` across the bands, each band at its own rate, with anything
158
+ * above the final threshold staying on the final band's rate.
159
+ *
160
+ * @param {number} quantity students or placements, depending on the product.
161
+ * @param {Array} bands each band's upper threshold and its rate.
162
+ * @return {Array} one entry per band that charged something.
163
+ */
91
164
  function graduated(quantity: number, bands: {limit: number, rate: number}[]): PricingBand[] {
92
165
  if (!(quantity > 0) || !bands.length) return [];
93
166
 
@@ -100,6 +173,8 @@ function graduated(quantity: number, bands: {limit: number, rate: number}[]): Pr
100
173
  if (inBand > 0) lines.push({tier: index + 1, quantity: inBand, rate: band.rate, amount: inBand * band.rate});
101
174
  });
102
175
 
176
+ // Everything past the last threshold keeps paying the last rate, so it belongs
177
+ // in the last band rather than as a second row at the same rate.
103
178
  const last = bands[bands.length - 1];
104
179
  const overflow = Math.max(quantity - last.limit, 0);
105
180
  if (overflow > 0) {
@@ -121,6 +196,16 @@ const clampFraction = (value?: number) => Math.min(Math.max(value || 0, 0), 1);
121
196
 
122
197
  const bandsTotal = (bands: PricingBand[]) => sum(bands.map((b) => b.amount));
123
198
 
199
+ /**
200
+ * What a trust comes off its quote for its size.
201
+ *
202
+ * A trust is more than one school, so a count below two is a single school and
203
+ * earns nothing — the discount is for bringing schools with you.
204
+ *
205
+ * @param {number} [schools] schools in the trust.
206
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
207
+ * @return {number} the discount, as a fraction of the quote.
208
+ */
124
209
  export function trustDiscountRate(
125
210
  schools?: number,
126
211
  config: PricingConfig = DEFAULT_PRICING_CONFIG,
@@ -130,6 +215,16 @@ export function trustDiscountRate(
130
215
  return count >= config.trust.threshold ? config.trust.largeDiscount : config.trust.discount;
131
216
  }
132
217
 
218
+ /**
219
+ * Price a school, trust or hub-school quote.
220
+ *
221
+ * Products the school isn't taking are left out of `lines` entirely, so the
222
+ * breakdown only ever shows what is being charged for.
223
+ *
224
+ * @param {object} input the roll, the year groups and the products taken.
225
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
226
+ * @return {object} the total, and the working that reached it.
227
+ */
133
228
  export function calculateSchoolPrice(
134
229
  input: SchoolPricingInput,
135
230
  config: PricingConfig = DEFAULT_PRICING_CONFIG,
@@ -196,6 +291,13 @@ export function calculateSchoolPrice(
196
291
  };
197
292
  }
198
293
 
294
+ /**
295
+ * Price a careers hub: the platform fee, plus a fee for every school on it.
296
+ *
297
+ * @param {number} schools how many schools the hub brings on.
298
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
299
+ * @return {object} the platform fee, the school fees and the total.
300
+ */
199
301
  export function calculateHubPrice(
200
302
  schools: number,
201
303
  config: PricingConfig = DEFAULT_PRICING_CONFIG,
@@ -204,6 +306,16 @@ export function calculateHubPrice(
204
306
  return {baseFee: config.hub.baseFee, schoolFees, total: config.hub.baseFee + schoolFees};
205
307
  }
206
308
 
309
+ /**
310
+ * Fill a stored config out with the defaults.
311
+ *
312
+ * The Firestore document is hand-edited, so treat every branch of it as
313
+ * possibly absent — a config saved before a tier was added should still price,
314
+ * rather than throwing on a missing rate.
315
+ *
316
+ * @param {object} [stored] whatever the Firestore document holds.
317
+ * @return {object} a complete config, safe to price with.
318
+ */
207
319
  export function mergePricingConfig(stored?: Partial<PricingConfig>): PricingConfig {
208
320
  const d = DEFAULT_PRICING_CONFIG;
209
321
  if (!stored) return d;
@@ -219,27 +331,59 @@ export function mergePricingConfig(stored?: Partial<PricingConfig>): PricingConf
219
331
  };
220
332
  }
221
333
 
334
+ /* -------------------------------------------------------------------------
335
+ * An institute's own quote.
336
+ *
337
+ * The calculator above prices a hypothetical school. What an actual customer
338
+ * pays is that calculation plus the terms agreed with them — the year groups
339
+ * and roll we quoted on, which discounts we gave, and any price we simply shook
340
+ * hands on — so those live on the institute, in `InstitutePricing`, and the
341
+ * quote is worked out from the two together.
342
+ *
343
+ * A school never reads the pricing configuration itself (it is admin data, and
344
+ * a school has no business seeing the tiers), so the agreed quote is snapshotted
345
+ * onto the institute as `InstitutePricing.quote` whenever an admin saves. That
346
+ * snapshot is what the school's own billing page shows.
347
+ * ---------------------------------------------------------------------- */
348
+
349
+ /** The terms agreed with one customer. Stored at `institutes/{oId}.pricing`. */
222
350
  export type InstitutePricing = {
351
+ /** The roll we quoted on. Defaults to the institute's own student count. */
223
352
  students?: number,
353
+ /** Y10 placements quoted. Undefined means the year group isn't on work experience. */
224
354
  wexY10?: number,
355
+ /** Y12 placements quoted. Undefined means the year group isn't on work experience. */
225
356
  wexY12?: number,
357
+ /** Take the partial-product discount off the events fee. */
226
358
  eventsDiscount?: boolean,
359
+ /** Take the partial-product discount off the destinations fee. */
227
360
  destinationsDiscount?: boolean,
361
+ /** Take the full-platform discount off the whole quote. */
228
362
  fullPlatformDiscount?: boolean,
363
+ /** A negotiated discount off the whole quote, as a fraction (0.1 = 10% off). */
229
364
  bespokeDiscount?: number,
365
+ /** Why the negotiated discount or the agreed price was given. Admin-only. */
230
366
  discountReason?: string,
367
+ /** A price agreed with the customer, which replaces the calculated one. */
231
368
  quoteOverride?: number,
369
+ /** Schools in the trust, which set its discount. Trust accounts only. */
232
370
  schools?: number,
371
+ /** Schools on the hub. Hub accounts only. */
233
372
  hubSchools?: number,
373
+ /** The quote as last saved, for the customer to see. */
234
374
  quote?: InstituteQuoteSnapshot,
235
375
  };
236
376
 
377
+ /** A priced institute: the same shape for a school, a trust and a hub. */
237
378
  export type InstituteQuote = {
238
379
  kind: "school"|"hub",
380
+ /** Every fee charged, with the working behind it. */
239
381
  lines: PricingLine[],
382
+ /** The lines added up, before any whole-quote discount. */
240
383
  subtotal: number,
241
384
  fullPlatformDiscountApplied: number,
242
385
  trustDiscountApplied: number,
386
+ /** The rate the trust discount was taken at, as a fraction. */
243
387
  trustDiscountRate: number,
244
388
  bespokeDiscountApplied: number,
245
389
  minFeeApplied: boolean,
@@ -247,23 +391,42 @@ export type InstituteQuote = {
247
391
  total: number,
248
392
  };
249
393
 
394
+ /** What the customer is shown: the total, and what makes it up. No rates. */
250
395
  export type InstituteQuoteSnapshot = {
251
396
  lines: {key: PricingLineKey, label: string, amount: number}[],
397
+ /** The lines added up. */
252
398
  subtotal: number,
399
+ /** Everything taken off the lines to reach the total, as one figure. */
253
400
  discountTotal: number,
401
+ /** True when the total is a floor or an agreed price above what the lines
402
+ * come to, so the breakdown is shown as an illustration, not a sum. */
254
403
  minimum: boolean,
255
404
  total: number,
405
+ /** ISO date the quote was last worked out. */
256
406
  updated: string,
257
407
  };
258
408
 
409
+ /** What the quote needs to know about the institute being priced. */
259
410
  export type InstituteQuoteInput = {
411
+ /** What the institute has, as resolved by `resolveProducts`. */
260
412
  products: {wex?: boolean, events?: boolean, destinations?: boolean, isHub?: boolean, isTrust?: boolean},
413
+ /** The institute's roll, when the agreed terms don't name one. */
261
414
  students?: number,
415
+ /** Schools in the trust, when the agreed terms don't name a number. */
262
416
  schools?: number,
417
+ /** Schools on the hub, when the agreed terms don't name a number. */
263
418
  hubSchools?: number,
419
+ /** The terms agreed with this customer. */
264
420
  pricing?: InstitutePricing,
265
421
  };
266
422
 
423
+ /**
424
+ * Price a school and present it as an institute quote.
425
+ *
426
+ * @param {object} input the roll, the year groups, the products and the terms.
427
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
428
+ * @return {object} the quote, ready to render.
429
+ */
267
430
  export function schoolQuote(
268
431
  input: SchoolPricingInput,
269
432
  config: PricingConfig = DEFAULT_PRICING_CONFIG,
@@ -271,6 +434,18 @@ export function schoolQuote(
271
434
  return {kind: "school", ...calculateSchoolPrice(input, config)};
272
435
  }
273
436
 
437
+ /**
438
+ * Price a careers hub as an institute quote: the platform fee, then the schools.
439
+ *
440
+ * Hubs have no products to discount individually and no minimum fee to fall back
441
+ * on, so the only terms that reach them are the negotiated discount and an
442
+ * agreed price.
443
+ *
444
+ * @param {number} schools how many schools the hub brings on.
445
+ * @param {object} [pricing] the terms agreed with this hub.
446
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
447
+ * @return {object} the quote, ready to render.
448
+ */
274
449
  export function hubQuote(
275
450
  schools: number,
276
451
  pricing?: InstitutePricing,
@@ -309,6 +484,21 @@ export function hubQuote(
309
484
  };
310
485
  }
311
486
 
487
+ /**
488
+ * Price an institute on what it actually has and what was agreed with it.
489
+ *
490
+ * The products come from `resolveProducts` rather than the raw flags, so a trust
491
+ * is priced on the union of its schools' products the same way it is gated on
492
+ * them, and it is a trust — not any school with a number against it — that earns
493
+ * the trust discount. A year group with no placements agreed is a year group not on work
494
+ * experience — except where the institute has work experience and neither year
495
+ * group has been filled in, which is a quote waiting to be finished rather than
496
+ * a school not taking the product, so Y10 is charged at zero to say so.
497
+ *
498
+ * @param {object} input the institute's products, roll and agreed terms.
499
+ * @param {object} [config] the figures to price with. Defaults to the 2026 sheet.
500
+ * @return {object} the quote, ready to render.
501
+ */
312
502
  export function instituteQuote(
313
503
  input: InstituteQuoteInput,
314
504
  config: PricingConfig = DEFAULT_PRICING_CONFIG,
@@ -339,6 +529,16 @@ export function instituteQuote(
339
529
  }, config);
340
530
  }
341
531
 
532
+ /**
533
+ * Reduce a quote to what the customer sees: the fees, and one discount figure.
534
+ *
535
+ * The base fee isn't itemised to customers, so it is folded into the first fee
536
+ * they are shown — and where there is nothing to fold it into, it stands alone
537
+ * as the platform fee it is.
538
+ *
539
+ * @param {object} quote the priced institute.
540
+ * @return {object} the snapshot to store on the institute.
541
+ */
342
542
  export function quoteSnapshot(quote: InstituteQuote): InstituteQuoteSnapshot {
343
543
  const itemised = quote.lines.filter((l) => l.key !== "base");
344
544
  const base = quote.lines.find((l) => l.key === "base")?.amount || 0;
@@ -356,6 +556,9 @@ export function quoteSnapshot(quote: InstituteQuote): InstituteQuoteSnapshot {
356
556
  return {
357
557
  lines,
358
558
  subtotal,
559
+ // The customer is shown the fees and one discount, so whatever separates
560
+ // the two — the whole-quote discounts, an agreed price below them — is
561
+ // that one figure, and a total above the fees is flagged instead.
359
562
  discountTotal: Math.max(subtotal - quote.total, 0),
360
563
  minimum: quote.total > subtotal,
361
564
  total: quote.total,