@cyc-seattle/platform 1.48.0 → 1.50.0

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.
@@ -18,7 +18,7 @@ export interface Entry {
18
18
  line_name: string;
19
19
  explanation: string;
20
20
  stage: Stage;
21
- /** How much of the amount MTDC counts: 0 when excluded, a fraction for a mix of rented and owned RIBs. */
21
+ /** How much of the amount MTDC counts: 1, or 0 when it leaves MTDC, such as a boat charter. */
22
22
  mtdc_share: number;
23
23
  /** Whether it's wages, which payroll tax is charged on. */
24
24
  wages: boolean;
@@ -1,7 +1,6 @@
1
1
  /** The warnings about what the blocks are given, or when they run. The rest are about the data. */
2
2
  const ALLOCATION_WARNINGS = new Set([
3
3
  "over_inventory_boats",
4
- "over_inventory_resource",
5
4
  "over_capacity",
6
5
  "unallocated_activity",
7
6
  "closed_date",
@@ -12,9 +11,9 @@ const COST_TYPES = {
12
11
  coach_wages: "coach wages",
13
12
  salaries: "salaries",
14
13
  payroll_tax: "payroll tax",
15
- ribs: "RIBs",
14
+ ribs: "coach boats",
16
15
  boat_maintenance: "boat maintenance",
17
- resources: "resources",
16
+ resources: "boat charters",
18
17
  travel: "travel",
19
18
  cogs: "cost of goods sold",
20
19
  };
@@ -43,9 +43,17 @@ export declare class Staffing {
43
43
  * Boats count the ones sailors bring.
44
44
  */
45
45
  private checkCapacity;
46
+ /** Books a cost on a block, split among its activities by their offerings' sailors. */
47
+ private book;
46
48
  /**
47
- * Warns when the organization's boats of one class, or a resource such as RIBs, are given to more
48
- * blocks at once on a date than it has. Boats sailors bring don't count.
49
+ * Charges each boat class's charter rate once a day, for the most of its boats chartered at once
50
+ * that day, shared among the blocks that charter them by how many each does. A charter is a rental,
51
+ * so it's left out of MTDC (2 CFR 200.1).
52
+ */
53
+ private bookCharters;
54
+ /**
55
+ * Warns when more of the organization's boats of one class are given to blocks at once on a date
56
+ * than it has that day on the asset list. Boats it charters, or sailors bring, don't count.
49
57
  */
50
58
  private checkInventory;
51
59
  /**
@@ -53,7 +61,7 @@ export declare class Staffing {
53
61
  * both. Returns each position's double-booked hours and dates.
54
62
  */
55
63
  private checkDoubleBooked;
56
- /** Costs every block's staff, RIBs, boats and resources, and checks them against the limits. */
64
+ /** Costs every block's staff and boats, and checks them against the limits. */
57
65
  run(scheduled: ScheduledActivity[]): {
58
66
  activities: ComputedActivity[];
59
67
  coaching: SalariedCoaching[];
@@ -61,7 +61,6 @@ export class Staffing {
61
61
  blocks(scheduled) {
62
62
  const { data } = this.ctx;
63
63
  const staff = groupBy(data.plan_block_staff, (r) => r.block_id);
64
- const resources = groupBy(data.plan_block_resources, (r) => r.block_id);
65
64
  const boats = groupBy(data.plan_block_boats, (r) => r.block_id);
66
65
  const rows = new Map(data.plan_activity_blocks.map((b) => [b.id, b]));
67
66
  const members = new Map();
@@ -103,7 +102,6 @@ export class Staffing {
103
102
  Math.max(0, ...list.map((a) => a.stored.putaway_minutes ?? 0)),
104
103
  on_water_minutes: set.length > 0 ? Math.max(...set) : sailingMinutes,
105
104
  staff: staff.get(id) ?? [],
106
- resources: resources.get(id) ?? [],
107
105
  boats: boats.get(id) ?? [],
108
106
  shares: new Map(list.map((a) => [a, share(a)])),
109
107
  });
@@ -171,12 +169,13 @@ export class Staffing {
171
169
  const classes = new Map(ctx.data.boat_classes.map((c) => [c.id, c]));
172
170
  let seats = 0;
173
171
  for (const b of block.boats) {
174
- const perBoat = classes.get(b.boat_class_id)?.sailors_per_boat;
175
- if (perBoat == null || perBoat <= 0) {
176
- this.warnOnce("no_sailors_per_boat", `${classes.get(b.boat_class_id)?.name ?? b.boat_class_id} has no sailors per boat. Add it to check that the boats hold every sailor`, b.boat_class_id, scope);
172
+ // A coach boat's class takes 0, without a warning.
173
+ const perBoat = classes.get(b.boat_class_id)?.max_capacity;
174
+ if (perBoat == null) {
175
+ this.warnOnce("no_max_capacity", `${classes.get(b.boat_class_id)?.name ?? b.boat_class_id} has no max capacity. Add it to check that the boats hold every sailor`, b.boat_class_id, scope);
177
176
  continue;
178
177
  }
179
- seats += (b.count + (b.sailor_owned ?? 0)) * perBoat;
178
+ seats += (b.count + (b.chartered ?? 0) + (b.sailor_owned ?? 0)) * perBoat;
180
179
  }
181
180
  if (sailors > seats) {
182
181
  ctx.warn("over_capacity", seats === 0
@@ -184,28 +183,79 @@ export class Staffing {
184
183
  : `${has} ${countOf(sailors, "sailor")}, but the boats hold only ${seats}`, block.row.key, scope);
185
184
  }
186
185
  }
186
+ /** Books a cost on a block, split among its activities by their offerings' sailors. */
187
+ book(block, account, amount, line, name, explanation, mtdcShare = 1, wages = false) {
188
+ if (amount === 0 || account === undefined)
189
+ return;
190
+ for (const [a, share] of block.shares) {
191
+ this.ctx.book({
192
+ cost_center_type: "offering",
193
+ cost_center_key: a.offering.key,
194
+ account_id: account,
195
+ function: this.ctx.functionFor(account, null, "offering"),
196
+ amount: amount * share,
197
+ line_key: `${line}:${a.offering.key}`,
198
+ line_name: name,
199
+ explanation,
200
+ mtdc_share: mtdcShare,
201
+ wages,
202
+ });
203
+ }
204
+ }
205
+ /**
206
+ * Charges each boat class's charter rate once a day, for the most of its boats chartered at once
207
+ * that day, shared among the blocks that charter them by how many each does. A charter is a rental,
208
+ * so it's left out of MTDC (2 CFR 200.1).
209
+ */
210
+ bookCharters(blocks) {
211
+ const { ctx } = this;
212
+ const rates = new Map(ctx.data.plan_boat_rates.map((r) => [r.boat_class_id, r]));
213
+ const classes = new Map(ctx.data.boat_classes.map((c) => [c.id, c]));
214
+ for (const list of groupBy(blocks, (b) => b.row.date).values()) {
215
+ const chartered = list.flatMap((block) => block.boats.flatMap((b) => ((b.chartered ?? 0) > 0 ? [{ block, id: b.boat_class_id, n: b.chartered }] : [])));
216
+ for (const [id, uses] of groupBy(chartered, (c) => c.id)) {
217
+ const name = classes.get(id)?.name ?? id;
218
+ const rate = rates.get(id)?.charter_per_day_cents;
219
+ if (rate == null) {
220
+ for (const { block } of uses) {
221
+ this.warnOnce("no_charter_rate", `${block.row.name} charters ${pluralOf(name)}, but the Operations Plan has no charter rate for them, so they cost nothing`, id, { version_id: block.row.version_id, date: block.row.date });
222
+ }
223
+ continue;
224
+ }
225
+ const day = mostAtOnce(uses).total * rate;
226
+ const total = sum(uses.map((u) => u.n));
227
+ for (const { block, n } of uses) {
228
+ this.book(block, ctx.costAccount("resources"), (day * n) / total, `charters:${id}`, `Boat charters: ${name}`, "The most boats of the class chartered at once that day × the class's charter rate", 0);
229
+ }
230
+ }
231
+ }
232
+ }
187
233
  /**
188
- * Warns when the organization's boats of one class, or a resource such as RIBs, are given to more
189
- * blocks at once on a date than it has. Boats sailors bring don't count.
234
+ * Warns when more of the organization's boats of one class are given to blocks at once on a date
235
+ * than it has that day on the asset list. Boats it charters, or sailors bring, don't count.
190
236
  */
191
237
  checkInventory(blocks) {
192
238
  const { ctx } = this;
193
239
  const dates = groupBy(blocks, (b) => b.row.date);
194
240
  const classes = new Map(ctx.data.boat_classes.map((c) => [c.id, c]));
195
- const fleet = new Map(ctx.data.plan_boat_rates.map((r) => [r.boat_class_id, r.quantity]));
196
- const resources = new Map(ctx.data.plan_resources.map((r) => [r.key, r]));
241
+ const assets = new Map(ctx.data.assets.map((a) => [a.id, a]));
242
+ const boats = ctx.data.boats.flatMap((b) => {
243
+ const asset = assets.get(b.asset_id);
244
+ return asset === undefined ? [] : [{ asset, boat_class_id: b.boat_class_id }];
245
+ });
197
246
  const organization = ctx.data.organization.name;
198
247
  for (const [date, list] of dates) {
248
+ const has = (a) => (a.acquired_on == null || a.acquired_on <= date) && (a.disposed_on == null || date < a.disposed_on);
199
249
  const check = (code, what, have, used) => {
200
- if (have == null)
201
- return;
202
250
  const out = list.flatMap((b) => (used(b) > 0 ? [{ block: b, n: used(b) }] : []));
203
251
  const peak = mostAtOnce(out);
204
252
  if (peak.total <= have)
205
253
  return;
254
+ const only = have === 0 ? "none" : `only ${have}`;
255
+ const count = countOf(peak.total, what);
206
256
  const message = peak.at.length === 1
207
- ? `${countOf(peak.total, what)} out at once for ${offeringsOf(peak.at[0].block)}, but ${organization} has only ${have}`
208
- : `${countOf(peak.total, what)} out at once, but ${organization} has only ${have}: ${listOf(peak.at.map(({ block, n }) => `${n} for ${offeringsOf(block)}`))}`;
257
+ ? `${count} out at once for ${offeringsOf(peak.at[0].block)}, but ${organization} has ${only}`
258
+ : `${count} out at once, but ${organization} has ${only}: ${listOf(peak.at.map(({ block, n }) => `${n} for ${offeringsOf(block)}`))}`;
209
259
  for (const version of new Set(peak.at.map(({ block }) => block.row.version_id))) {
210
260
  ctx.warn(code, message, what, {
211
261
  version_id: version,
@@ -215,17 +265,8 @@ export class Staffing {
215
265
  };
216
266
  const classIds = new Set(list.flatMap((b) => b.boats.filter((x) => x.count > 0).map((x) => x.boat_class_id)));
217
267
  for (const id of classIds) {
218
- if (fleet.get(id) == null) {
219
- for (const b of list.filter((x) => x.boats.some((y) => y.boat_class_id === id && y.count > 0))) {
220
- this.warnOnce("no_fleet_quantity", `The Operations Plan doesn't say how many ${pluralOf(classes.get(id)?.name ?? id)} ${organization} has. Add it to check that there are enough`, id, { version_id: b.row.version_id, date });
221
- }
222
- }
223
- check("over_inventory_boats", classes.get(id)?.name ?? id, fleet.get(id), (b) => sum(b.boats.filter((x) => x.boat_class_id === id).map((x) => x.count)));
224
- }
225
- const resourceKeys = new Set(list.flatMap((b) => b.resources.map((x) => x.resource_key)));
226
- for (const key of resourceKeys) {
227
- const resource = resources.get(key);
228
- check("over_inventory_resource", resource?.name ?? key, resource?.quantity, (b) => sum(b.resources.filter((x) => x.resource_key === key).map((x) => x.quantity)));
268
+ const have = boats.filter((b) => b.boat_class_id === id && has(b.asset)).length;
269
+ check("over_inventory_boats", classes.get(id)?.name ?? id, have, (b) => sum(b.boats.filter((x) => x.boat_class_id === id).map((x) => x.count)));
229
270
  }
230
271
  }
231
272
  }
@@ -260,7 +301,7 @@ export class Staffing {
260
301
  }
261
302
  return result;
262
303
  }
263
- /** Costs every block's staff, RIBs, boats and resources, and checks them against the limits. */
304
+ /** Costs every block's staff and boats, and checks them against the limits. */
264
305
  run(scheduled) {
265
306
  const { ctx } = this;
266
307
  const blocks = this.blocks(scheduled);
@@ -274,37 +315,14 @@ export class Staffing {
274
315
  const coaching = [];
275
316
  const levels = new Map(ctx.data.plan_staff_levels.map((l) => [l.key, l]));
276
317
  const positions = new Map(ctx.data.plan_salaried_positions.map((p) => [p.key, p]));
277
- const resources = new Map(ctx.data.plan_resources.map((r) => [r.key, r]));
278
318
  const boatClasses = new Map(ctx.data.boat_classes.map((c) => [c.id, c]));
279
319
  const boatRates = new Map(ctx.data.plan_boat_rates.map((r) => [r.boat_class_id, r]));
280
- const ribKind = ctx.data.rib_resource_kind_id;
281
320
  const given = new Map();
282
321
  for (const block of byDate(blocks)) {
283
322
  const scope = { version_id: block.row.version_id, date: block.row.date };
284
323
  const paidHours = block.paid_minutes / 60;
285
324
  const waterHours = block.on_water_minutes / 60;
286
- // Each cost is split among the block's activities by their offerings' sailors.
287
- const book = (type, amount, line, name, explanation, mtdcShare = 1) => {
288
- if (amount === 0)
289
- return;
290
- const account = ctx.costAccount(type);
291
- if (account === undefined)
292
- return;
293
- for (const [a, share] of block.shares) {
294
- ctx.book({
295
- cost_center_type: "offering",
296
- cost_center_key: a.offering.key,
297
- account_id: account,
298
- function: ctx.functionFor(account, null, "offering"),
299
- amount: amount * share,
300
- line_key: `${line}:${a.offering.key}`,
301
- line_name: name,
302
- explanation,
303
- mtdc_share: mtdcShare,
304
- wages: type === "coach_wages",
305
- });
306
- }
307
- };
325
+ const book = (type, amount, line, name, explanation) => this.book(block, ctx.costAccount(type), amount, line, name, explanation, 1, type === "coach_wages");
308
326
  let wages = 0;
309
327
  for (const s of block.staff) {
310
328
  if (s.count <= 0)
@@ -350,32 +368,20 @@ export class Staffing {
350
368
  wages += s.count * paidHours * (level.hourly_rate_cents ?? 0);
351
369
  }
352
370
  book("coach_wages", wages, "coach_wages", "Coach wages", "Staff × paid hours × each one's hourly rate");
353
- let ribs = 0;
354
- for (const use of block.resources) {
355
- const resource = resources.get(use.resource_key);
356
- if (resource === undefined) {
357
- this.warnOnce("unknown_resource", `${block.row.name} uses resource ${use.resource_key}, which isn't in the plan`, use.resource_key, scope);
358
- continue;
359
- }
360
- const rib = ribKind != null && resource.resource_kind_id === ribKind;
361
- if (rib)
362
- ribs += use.quantity;
363
- const hours = use.hours ?? (rib ? waterHours : paidHours);
364
- const cost = use.quantity * (hours * hourlyCost(resource) + (resource.cost_per_day_cents ?? 0));
365
- book(rib ? "ribs" : "resources", cost, rib ? "ribs" : `resource:${resource.key}`, rib ? "RIBs" : resource.name, `${resource.name}: quantity × hours × hourly cost, plus a day rate`, resource.excluded_from_mtdc ? 0 : 1);
366
- }
367
371
  for (const b of block.boats) {
368
372
  const rate = boatRates.get(b.boat_class_id);
369
373
  if (rate === undefined || b.count <= 0 || waterHours <= 0)
370
374
  continue;
371
- book("boat_maintenance", b.count * waterHours * rate.maintenance_per_boat_hour_cents, `boats:${b.boat_class_id}`, `Boat maintenance: ${boatClasses.get(b.boat_class_id)?.name ?? "boats"}`, "The organization's boats × on-water hours × the class's maintenance rate");
375
+ const boatClass = boatClasses.get(b.boat_class_id);
376
+ this.book(block, boatClass?.account_id ?? ctx.costAccount("boat_maintenance"), b.count * waterHours * (rate.maintenance_per_boat_hour_cents + (rate.fuel_per_hour_cents ?? 0)), `boats:${b.boat_class_id}`, `Boats: ${boatClass?.name ?? "boats"}`, "The organization's boats × on-water hours × the class's fuel and upkeep");
372
377
  }
373
378
  const coaches = sum(block.staff.map((s) => s.count));
374
- const boats = sum(block.boats.map((b) => b.count));
379
+ const boats = sum(block.boats.map((b) => b.count + (b.chartered ?? 0)));
375
380
  for (const [a, share] of block.shares) {
376
- given.set(a, { coaches: coaches * share, ribs: ribs * share, boats: boats * share });
381
+ given.set(a, { coaches: coaches * share, boats: boats * share });
377
382
  }
378
383
  }
384
+ this.bookCharters(byDate(blocks));
379
385
  this.bookTrips(blocks, scheduled);
380
386
  return {
381
387
  coaching,
@@ -391,7 +397,6 @@ export class Staffing {
391
397
  on_water_minutes: a.on_water_minutes,
392
398
  uses_boats: a.uses_boats,
393
399
  coaches: given.get(a)?.coaches ?? 0,
394
- ribs: given.get(a)?.ribs ?? 0,
395
400
  boats: given.get(a)?.boats ?? 0,
396
401
  block_id: blocks.has(a.stored.block_id ?? "") ? a.stored.block_id : null,
397
402
  })),
@@ -508,9 +513,6 @@ function extraCoachMinutes(list) {
508
513
  }
509
514
  return minutes;
510
515
  }
511
- function hourlyCost(r) {
512
- return (r.cost_per_hour_cents ?? 0) + (r.fuel_per_hour_cents ?? 0) + (r.maintenance_per_hour_cents ?? 0);
513
- }
514
516
  export function round2(n) {
515
517
  return Math.round(n * 100) / 100;
516
518
  }
@@ -52,12 +52,27 @@ export interface QualificationCover {
52
52
  export interface BoatClass {
53
53
  id: Id;
54
54
  name: string;
55
- sailors_per_boat: number | null;
55
+ /** The most sailors one boat can safely take. */
56
+ max_capacity: number | null;
56
57
  footprint_sq_ft: number | null;
58
+ /** Where its own boats' fuel and upkeep are booked; empty for the boat maintenance cost type's. */
59
+ account_id?: Id | null;
57
60
  }
58
- export interface ResourceKind {
61
+ /** Something the organization owns: a sailboat, a powerboat, a motor, or other equipment. */
62
+ export interface Asset {
59
63
  id: Id;
60
- name: string;
64
+ kind: "sailboat" | "powerboat" | "motor" | "other";
65
+ name: string | null;
66
+ /** The day the organization got it, or expects to; empty counts it from the start. */
67
+ acquired_on: DateString | null;
68
+ /** The day it stopped being the organization's; empty while it still is. */
69
+ disposed_on: DateString | null;
70
+ }
71
+ /** A sailboat or powerboat asset's class. */
72
+ export interface Boat {
73
+ id: Id;
74
+ asset_id: Id;
75
+ boat_class_id: Id;
61
76
  }
62
77
  export interface Organization {
63
78
  id: Id;
@@ -88,7 +103,8 @@ export type ActivityKind = "practice" | "regatta" | "clinic" | "travel";
88
103
  export interface PlanOffering extends Versioned {
89
104
  product_id: Id;
90
105
  name: string;
91
- season_term: string | null;
106
+ /** A `plan_terms` key in the Operations Plan, or none. */
107
+ term_key: Id | null;
92
108
  starts_on: DateString;
93
109
  ends_on: DateString;
94
110
  capacity: number | null;
@@ -133,7 +149,7 @@ export interface PlanActivity extends Versioned {
133
149
  /** The block it's staffed in, if any. */
134
150
  block_id: Id | null;
135
151
  }
136
- /** Activities on one date that share their staff, RIBs and boats. */
152
+ /** Activities on one date that share their staff and boats. */
137
153
  export interface PlanActivityBlock extends Versioned {
138
154
  date: DateString;
139
155
  name: string;
@@ -149,21 +165,13 @@ export interface PlanBlockStaff extends Versioned {
149
165
  /** How a salaried position is paid for the block: out of its salary (the default), or by the hour at its own rate. */
150
166
  pay: "salary" | "hourly" | null;
151
167
  }
152
- /** A resource on a block, such as RIBs. */
153
- export interface PlanBlockResource extends Versioned {
154
- block_id: Id;
155
- /** A `plan_resources` key. */
156
- resource_key: Id;
157
- quantity: number;
158
- /** Hours each is used; empty for the block's time on the water (RIBs) or its paid time (the rest). */
159
- hours: number | null;
160
- }
161
- /** Boats of one class on a block: the organization's, and the ones sailors bring. */
168
+ /** Boats of one class on a block: the organization's, the ones it charters, and the ones sailors bring. */
162
169
  export interface PlanBlockBoats extends Versioned {
163
170
  block_id: Id;
164
171
  boat_class_id: Id;
165
172
  count: number;
166
173
  sailor_owned: number | null;
174
+ chartered?: number | null;
167
175
  }
168
176
  export interface PlanLaborLine extends Versioned {
169
177
  name: string;
@@ -230,22 +238,15 @@ export interface PlanSalariedBooking extends Versioned {
230
238
  kind: "management";
231
239
  hours: number;
232
240
  }
233
- export interface PlanResource extends Versioned {
234
- resource_kind_id: Id;
235
- name: string;
236
- quantity: number | null;
237
- cost_per_hour_cents: number | null;
238
- cost_per_day_cents: number | null;
239
- fuel_per_hour_cents: number | null;
240
- maintenance_per_hour_cents: number | null;
241
- /** Set for a rental, whose cost leaves MTDC. */
242
- excluded_from_mtdc: boolean;
243
- }
241
+ /** A boat class's rates for the year. */
244
242
  export interface PlanBoatRate extends Versioned {
245
243
  boat_class_id: Id;
244
+ /** Upkeep for each hour one of the organization's boats of the class is on the water. */
246
245
  maintenance_per_boat_hour_cents: number;
247
- /** How many boats of the class the organization has. */
248
- quantity: number | null;
246
+ /** Fuel for each hour one is on the water. */
247
+ fuel_per_hour_cents?: number | null;
248
+ /** What one costs to charter for a day. */
249
+ charter_per_day_cents?: number | null;
249
250
  }
250
251
  /** A group of shared costs, such as G&A, divided among all of the plan's offerings by MTDC. */
251
252
  export interface PlanPool extends Versioned {
@@ -281,21 +282,27 @@ export interface PlanClosedDate extends Versioned {
281
282
  date: DateString;
282
283
  name: string;
283
284
  }
285
+ /** A planning period in the Operations Plan, such as Summer 2027, which an offering can be in. */
286
+ export interface PlanTerm extends Versioned {
287
+ name: string;
288
+ starts_on: DateString;
289
+ ends_on: DateString;
290
+ }
284
291
  /** The catalog plus every row of the plan versions being calculated. */
285
292
  export interface PlanData {
286
293
  /** The plan year. Fiscal year N ends in calendar year N. */
287
294
  fiscal_year: number;
288
295
  organization: Organization;
289
296
  organization_cost_accounts: OrganizationCostAccount[];
290
- /** The resource kind whose `plan_resources` price RIB hours. */
291
- rib_resource_kind_id: Id | null;
292
297
  products: Product[];
293
298
  program_areas: ProgramArea[];
294
299
  accounts: Account[];
295
300
  qualifications: Qualification[];
296
301
  qualification_covers: QualificationCover[];
297
302
  boat_classes: BoatClass[];
298
- resource_kinds: ResourceKind[];
303
+ /** The asset list, with its boats, which the engine counts by class on each day. */
304
+ assets: Asset[];
305
+ boats: Boat[];
299
306
  /** The plans and versions being calculated, so the engine can tell which plan each offering is in. */
300
307
  plans: Plan[];
301
308
  plan_versions: PlanVersion[];
@@ -305,7 +312,6 @@ export interface PlanData {
305
312
  plan_activities: PlanActivity[];
306
313
  plan_activity_blocks: PlanActivityBlock[];
307
314
  plan_block_staff: PlanBlockStaff[];
308
- plan_block_resources: PlanBlockResource[];
309
315
  plan_block_boats: PlanBlockBoats[];
310
316
  plan_labor_lines: PlanLaborLine[];
311
317
  plan_budget_lines: PlanBudgetLine[];
@@ -314,13 +320,13 @@ export interface PlanData {
314
320
  plan_staff_levels: PlanStaffLevel[];
315
321
  plan_salaried_positions: PlanSalariedPosition[];
316
322
  plan_salaried_bookings: PlanSalariedBooking[];
317
- plan_resources: PlanResource[];
318
323
  plan_boat_rates: PlanBoatRate[];
319
324
  plan_pools: PlanPool[];
320
325
  plan_bonus_owners: PlanBonusOwner[];
321
326
  plan_bonus_owner_products: PlanBonusOwnerProduct[];
322
327
  plan_closed_dates?: PlanClosedDate[];
323
328
  plan_capital_items?: PlanCapitalItem[];
329
+ plan_terms?: PlanTerm[];
324
330
  }
325
331
  /** One line of cost or revenue for the plan year, on one offering, product, pool or the organization. */
326
332
  export interface CostLine {
@@ -370,9 +376,8 @@ export interface ComputedActivity {
370
376
  paid_minutes: number;
371
377
  on_water_minutes: number;
372
378
  uses_boats: boolean;
373
- /** Its share of its block's staff, RIBs and boats, by its sailors. */
379
+ /** Its share of its block's staff and boats, the organization's and chartered, by its sailors. */
374
380
  coaches: number;
375
- ribs: number;
376
381
  boats: number;
377
382
  block_id: Id | null;
378
383
  }
@@ -1,6 +1,7 @@
1
1
  /** Every collection, for `createDirectus<Schema>()`. */
2
2
  export interface Schema {
3
3
  accounts: Accounts[];
4
+ assets: Assets[];
4
5
  audit_findings: AuditFindings[];
5
6
  boat_classes: BoatClasses[];
6
7
  boats: Boats[];
@@ -33,7 +34,6 @@ export interface Schema {
33
34
  plan_activities: PlanActivities[];
34
35
  plan_activity_blocks: PlanActivityBlocks[];
35
36
  plan_block_boats: PlanBlockBoats[];
36
- plan_block_resources: PlanBlockResources[];
37
37
  plan_block_staff: PlanBlockStaff[];
38
38
  plan_boat_rates: PlanBoatRates[];
39
39
  plan_bonus_owner_products: PlanBonusOwnerProducts[];
@@ -46,7 +46,6 @@ export interface Schema {
46
46
  plan_pools: PlanPools[];
47
47
  plan_programs: PlanPrograms[];
48
48
  plan_rates: PlanRates[];
49
- plan_resources: PlanResources[];
50
49
  plan_result_capital: PlanResultCapital[];
51
50
  plan_result_lines: PlanResultLines[];
52
51
  plan_result_pnl: PlanResultPnl[];
@@ -57,6 +56,7 @@ export interface Schema {
57
56
  plan_salaried_bookings: PlanSalariedBookings[];
58
57
  plan_salaried_positions: PlanSalariedPositions[];
59
58
  plan_staff_levels: PlanStaffLevels[];
59
+ plan_terms: PlanTerms[];
60
60
  plan_trips: PlanTrips[];
61
61
  plan_versions: PlanVersions[];
62
62
  plans: Plans[];
@@ -69,7 +69,6 @@ export interface Schema {
69
69
  qualification_covers: QualificationCovers[];
70
70
  qualifications: Qualifications[];
71
71
  registrations: Registrations[];
72
- resource_kinds: ResourceKinds[];
73
72
  schools: Schools[];
74
73
  sync_leases: SyncLeases[];
75
74
  sync_runs: SyncRuns[];
@@ -114,6 +113,31 @@ export interface Accounts {
114
113
  /** Kept for history, and closed to new lines. */
115
114
  retired: boolean;
116
115
  }
116
+ /** Each boat, motor and piece of equipment CSC owns: sailboats, powerboats such as the coach boats, motors, and others such as trailers. Planning counts the boats of each class CSC has on each day. */
117
+ export interface Assets {
118
+ id: string;
119
+ /** A sailboat or powerboat also needs a row in Boats. Directus can't check that, so keep them matching. */
120
+ kind: "sailboat" | "powerboat" | "motor" | "other";
121
+ /** Its name, if it has one, such as Scooter or the van. */
122
+ name: string | null;
123
+ /** This asset's boat details, if it is a sailboat or powerboat. */
124
+ boats: (string | Boats)[];
125
+ /** Who made it, such as Zodiac or Yamaha. */
126
+ make: string | null;
127
+ /** Its model. A boat's model is its boat class, in Boats. */
128
+ model: string | null;
129
+ /** The number that identifies it: a boat's hull number (HIN), a vehicle's VIN, or a serial number. */
130
+ identifier: string | null;
131
+ /** The state registration number, such as WN 1234 AB, if it has one. */
132
+ registration_number: string | null;
133
+ /** Its book value, as in QuickBooks account 1500. In cents: 12500 is $125.00. */
134
+ value_cents: number | null;
135
+ /** The day CSC got it, or expects to. Planning counts it from this day; empty counts it from the start. */
136
+ acquired_on: string | null;
137
+ /** The day CSC sold, scrapped, or gave it away. Planning stops counting it on this day. Empty while CSC still has it. */
138
+ disposed_on: string | null;
139
+ note: string | null;
140
+ }
117
141
  /** Something a sync noticed but didn't fix. A dismissed finding stays dismissed on later runs. */
118
142
  export interface AuditFindings {
119
143
  id: string;
@@ -132,28 +156,22 @@ export interface AuditFindings {
132
156
  export interface BoatClasses {
133
157
  id: string;
134
158
  name: string;
135
- sailors_per_boat: number;
159
+ /** The most sailors one boat can safely take, such as 4 in an FJ. 0 for a coach boat. */
160
+ max_capacity: number;
136
161
  /** Dry-storage area one boat takes. */
137
162
  footprint_sq_ft: string | null;
163
+ /** Where the fuel and upkeep of CSC's own boats of the class are booked, such as Coach boats for a RIB. Empty books them to boat maintenance. */
164
+ account_id: string | Accounts | null;
138
165
  }
139
- /** Each boat CSC owns, for planning the year. Sailors' own boats aren't listed here. */
166
+ /** A boat's own details, sail or power: its class and sail number. Sailors' own boats aren't listed here. */
140
167
  export interface Boats {
141
168
  id: string;
169
+ /** The asset this boat is. Its kind must be Sailboat or Powerboat. */
170
+ asset_id: string | Assets;
171
+ /** Such as Opti, 420, or RIB. */
142
172
  boat_class_id: string | BoatClasses;
143
- /** The boat's name, if it has one. */
144
- name: string | null;
173
+ /** A sailboat's, if it has one. */
145
174
  sail_number: string | null;
146
- /** The hull identification number on the transom, if it's known. */
147
- hull_number: string | null;
148
- /** What the boat is worth. In cents: 12500 is $125.00. */
149
- value_cents: number | null;
150
- /** The day CSC got the boat. */
151
- acquired_on: string | null;
152
- /** The day the boat first sailed in a program. */
153
- in_service_on: string | null;
154
- /** The day CSC stopped using the boat. Empty while it's still in use. */
155
- retired_on: string | null;
156
- note: string | null;
157
175
  }
158
176
  /** An offering of a program that plans and Clubspot both point to, such as "J-Pod · fall practices" or "Summer camp · week 3". Each Financial Plan sets its own price, schedule, and size for it, and Clubspot's offerings are linked to it. */
159
177
  export interface CatalogOfferings {
@@ -557,20 +575,8 @@ export interface PlanBlockBoats {
557
575
  count: number;
558
576
  /** How many boats sailors bring. They cost nothing and don't count against the organization's. */
559
577
  sailor_owned: number | null;
560
- }
561
- /** RIBs and other equipment a block uses. */
562
- export interface PlanBlockResources {
563
- id: string;
564
- /** The version this row belongs to. */
565
- version_id: string | PlanVersions;
566
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
567
- key: string;
568
- block_id: string | PlanActivityBlocks;
569
- /** The resource it uses: copy the Key from a resource in the Operations Plan. */
570
- resource_key: string;
571
- quantity: string;
572
- /** Hours each is used. Leave it empty for the block's time on the water (RIBs) or its paid time (other equipment). */
573
- hours: string | null;
578
+ /** How many boats of the class the block charters. They cost the class's charter rate for each day, and don't count against the organization's. */
579
+ chartered: number | null;
574
580
  }
575
581
  /** Who coaches a block: a number of staff at one staff level, or a salaried position. */
576
582
  export interface PlanBlockStaff {
@@ -597,10 +603,12 @@ export interface PlanBoatRates {
597
603
  /** Stays the same across versions. Copy it where another row asks for this one's Key. */
598
604
  key: string;
599
605
  boat_class_id: string | BoatClasses;
600
- /** In cents: 12500 is $125.00. */
606
+ /** Upkeep for each hour one of CSC's boats of the class is on the water. In cents: 12500 is $125.00. */
601
607
  maintenance_per_boat_hour_cents: number;
602
- /** How many boats of the class the organization has. */
603
- quantity: number | null;
608
+ /** Fuel for each hour one of CSC's boats of the class is on the water. In cents: 12500 is $125.00. */
609
+ fuel_per_hour_cents: number | null;
610
+ /** What one boat of the class costs to charter for a day. In cents: 12500 is $125.00. */
611
+ charter_per_day_cents: number | null;
604
612
  }
605
613
  /** The products a bonus owner owns. */
606
614
  export interface PlanBonusOwnerProducts {
@@ -706,7 +714,8 @@ export interface PlanOfferings {
706
714
  key: string;
707
715
  product_id: string | Products;
708
716
  name: string;
709
- season_term: string | null;
717
+ /** The term it runs in: copy the Key from a term in the Operations Plan. Leave it empty for an offering in no term. */
718
+ term_key: string | null;
710
719
  starts_on: string;
711
720
  ends_on: string;
712
721
  capacity: number | null;
@@ -758,29 +767,6 @@ export interface PlanRates {
758
767
  lodging_per_night_cents: number | null;
759
768
  /** In cents: 12500 is $125.00. */
760
769
  mileage_rate_cents: number | null;
761
- /** The kind of resource whose cost prices coach boat (RIB) hours. */
762
- rib_resource_kind_id: string | ResourceKinds | null;
763
- }
764
- /** Equipment programs share, such as coach boats, and what an hour or a day of it costs. */
765
- export interface PlanResources {
766
- id: string;
767
- /** The version this row belongs to. */
768
- version_id: string | PlanVersions;
769
- /** Stays the same across versions. Copy it where another row asks for this one's Key. */
770
- key: string;
771
- resource_kind_id: string | ResourceKinds;
772
- name: string;
773
- quantity: number | null;
774
- /** In cents: 12500 is $125.00. */
775
- cost_per_hour_cents: number | null;
776
- /** In cents: 12500 is $125.00. */
777
- cost_per_day_cents: number | null;
778
- /** In cents: 12500 is $125.00. */
779
- fuel_per_hour_cents: number | null;
780
- /** In cents: 12500 is $125.00. */
781
- maintenance_per_hour_cents: number | null;
782
- /** Set for a rental, whose cost is left out of modified total direct costs (MTDC). */
783
- excluded_from_mtdc: boolean;
784
770
  }
785
771
  /** The capital list in rank order, in a Financial Plan, with what the year's money covers. A draft's are worked out again a moment after each change to it, and a saved one's when it's saved, and then never change. */
786
772
  export interface PlanResultCapital {
@@ -971,6 +957,20 @@ export interface PlanStaffLevels {
971
957
  /** In cents: 12500 is $125.00. */
972
958
  hourly_rate_cents: number | null;
973
959
  }
960
+ /** A planning period, such as Summer 2027, with its start and end dates. An offering in a Program Plan can be in one of its Financial Plan's terms. */
961
+ export interface PlanTerms {
962
+ id: string;
963
+ /** The version this row belongs to. */
964
+ version_id: string | PlanVersions;
965
+ /** Stays the same across versions. Copy it where another row asks for this one's Key. */
966
+ key: string;
967
+ /** What it's called, such as Summer 2027. */
968
+ name: string;
969
+ /** Its first day. */
970
+ starts_on: string;
971
+ /** Its last day. */
972
+ ends_on: string;
973
+ }
974
974
  /** A trip an offering travels for, such as an away regatta. */
975
975
  export interface PlanTrips {
976
976
  id: string;
@@ -1105,11 +1105,6 @@ export interface Registrations {
1105
1105
  status: "confirmed" | "waitlist" | "cancelled";
1106
1106
  clubspot_entry_id: string | ClubspotRegistrationEntries;
1107
1107
  }
1108
- /** A kind of shared resource, such as RIB or trailer. */
1109
- export interface ResourceKinds {
1110
- id: string;
1111
- name: string;
1112
- }
1113
1108
  /** The schools a sailor can sail for, which families pick from. A school's other names match what families typed on their Clubspot forms. */
1114
1109
  export interface Schools {
1115
1110
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyc-seattle/platform",
3
- "version": "1.48.0",
3
+ "version": "1.50.0",
4
4
  "description": "Pulumi components for CYC's shared Google Cloud platform: WebService, the platform outputs reader, and the stacks that build the platform",
5
5
  "author": "CYC Community Sailing Center",
6
6
  "license": "Apache-2.0",