@cyc-seattle/platform 1.47.0 → 1.49.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.
@@ -15,7 +15,7 @@ export interface ScheduledActivity {
15
15
  uses_boats: boolean;
16
16
  }
17
17
  /**
18
- * The plan year's activities. Paid time is start to end plus setup and put-away; on-water time is
18
+ * The plan's activities. Paid time is start to end plus setup and put-away; on-water time is
19
19
  * `on_water_minutes` if set, otherwise the whole span, and zero when the activity uses no boats.
20
20
  */
21
21
  export declare function scheduleActivities(ctx: Context): ScheduledActivity[];
@@ -1,6 +1,6 @@
1
1
  import { minutesOf } from "./dates.js";
2
2
  /**
3
- * The plan year's activities. Paid time is start to end plus setup and put-away; on-water time is
3
+ * The plan's activities. Paid time is start to end plus setup and put-away; on-water time is
4
4
  * `on_water_minutes` if set, otherwise the whole span, and zero when the activity uses no boats.
5
5
  */
6
6
  export function scheduleActivities(ctx) {
@@ -21,8 +21,6 @@ export function scheduleActivities(ctx) {
21
21
  ctx.warn("unknown_offering", `Activity ${activity.key} is for an offering that isn't in the plan`, activity.key);
22
22
  continue;
23
23
  }
24
- if (!ctx.inYear(offering))
25
- continue;
26
24
  const timed = activity.start_time != null && activity.end_time != null;
27
25
  const start = timed ? minutesOf(activity.start_time) : null;
28
26
  let end = timed ? minutesOf(activity.end_time) : null;
@@ -84,10 +84,7 @@ function baseOf(ctx, owner) {
84
84
  }
85
85
  products.add(row.product_id);
86
86
  }
87
- const offeringKeys = new Set(ctx
88
- .fyOfferings()
89
- .filter((o) => products.has(o.product_id))
90
- .map((o) => o.key));
87
+ const offeringKeys = new Set(ctx.data.plan_offerings.filter((o) => products.has(o.product_id)).map((o) => o.key));
91
88
  const afterPools = (owner.basis ?? "after_shared_costs") === "after_shared_costs";
92
89
  let net = 0;
93
90
  for (const e of ctx.entries) {
@@ -26,11 +26,6 @@ export interface Entry {
26
26
  export type EntryInput = Omit<Entry, "stage" | "mtdc_share" | "wages"> & Partial<Pick<Entry, "stage" | "mtdc_share" | "wages">>;
27
27
  /** Lookups over the input, and the entries recorded so far, shared by every step. */
28
28
  export declare class Context {
29
- /** The plan year's first and last days. */
30
- readonly fy: {
31
- from: DateString;
32
- to: DateString;
33
- };
34
29
  readonly entries: Entry[];
35
30
  readonly warnings: EngineWarning[];
36
31
  readonly accounts: Map<Id, Account>;
@@ -62,8 +57,4 @@ export declare class Context {
62
57
  accountUnder(accountId: Id, ancestorId: Id): boolean;
63
58
  /** The account a product's sales are booked to: its program area's, or its own outside an area. */
64
59
  revenueAccount(product: Product): Id | null;
65
- /** Whether the offering counts in the plan year: it does, whole, in the year it starts. */
66
- inYear(offering: PlanOffering): boolean;
67
- /** The plan year's offerings. */
68
- fyOfferings(): PlanOffering[];
69
60
  }
@@ -1,4 +1,3 @@
1
- import { fiscalYearDates, within } from "./dates.js";
2
1
  /** The warnings about what the blocks are given, or when they run. The rest are about the data. */
3
2
  const ALLOCATION_WARNINGS = new Set([
4
3
  "over_inventory_boats",
@@ -21,8 +20,6 @@ const COST_TYPES = {
21
20
  };
22
21
  /** Lookups over the input, and the entries recorded so far, shared by every step. */
23
22
  export class Context {
24
- /** The plan year's first and last days. */
25
- fy;
26
23
  entries = [];
27
24
  warnings = [];
28
25
  accounts;
@@ -41,7 +38,6 @@ export class Context {
41
38
  data;
42
39
  constructor(data) {
43
40
  this.data = data;
44
- this.fy = fiscalYearDates(data.fiscal_year, data.organization.fiscal_year_start_month);
45
41
  this.accounts = new Map(data.accounts.map((a) => [a.id, a]));
46
42
  this.products = new Map(data.products.map((p) => [p.id, p]));
47
43
  this.areas = new Map(data.program_areas.map((a) => [a.id, a]));
@@ -124,12 +120,4 @@ export class Context {
124
120
  }
125
121
  return area.revenue_account_id;
126
122
  }
127
- /** Whether the offering counts in the plan year: it does, whole, in the year it starts. */
128
- inYear(offering) {
129
- return within(offering.starts_on, this.fy.from, this.fy.to);
130
- }
131
- /** The plan year's offerings. */
132
- fyOfferings() {
133
- return this.data.plan_offerings.filter((o) => this.inYear(o));
134
- }
135
123
  }
@@ -1,12 +1,4 @@
1
1
  import type { DateString, TimeString } from "./types.ts";
2
- export declare function addDays(date: DateString, days: number): DateString;
3
2
  /** ISO weekday, 1 = Monday. */
4
3
  export declare function isoWeekday(date: DateString): number;
5
- /** Fiscal year N ends in calendar year N, so a July start makes FY2027 2026-07-01 to 2027-06-30. */
6
- export declare function fiscalYearDates(fiscalYear: number, startMonth: number): {
7
- from: DateString;
8
- to: DateString;
9
- };
10
4
  export declare function minutesOf(time: TimeString): number;
11
- /** Whether `date` falls in a window whose open ends are unbounded. */
12
- export declare function within(date: DateString, from: DateString | null | undefined, to: DateString | null | undefined): boolean;
@@ -1,31 +1,12 @@
1
1
  function toUtc(date) {
2
2
  return new Date(`${date}T00:00:00Z`);
3
3
  }
4
- function fromUtc(d) {
5
- return d.toISOString().slice(0, 10);
6
- }
7
- export function addDays(date, days) {
8
- const d = toUtc(date);
9
- d.setUTCDate(d.getUTCDate() + days);
10
- return fromUtc(d);
11
- }
12
4
  /** ISO weekday, 1 = Monday. */
13
5
  export function isoWeekday(date) {
14
6
  const day = toUtc(date).getUTCDay();
15
7
  return day === 0 ? 7 : day;
16
8
  }
17
- /** Fiscal year N ends in calendar year N, so a July start makes FY2027 2026-07-01 to 2027-06-30. */
18
- export function fiscalYearDates(fiscalYear, startMonth) {
19
- const startYear = startMonth === 1 ? fiscalYear : fiscalYear - 1;
20
- const from = `${startYear}-${String(startMonth).padStart(2, "0")}-01`;
21
- const nextYear = `${startYear + 1}-${String(startMonth).padStart(2, "0")}-01`;
22
- return { from, to: addDays(nextYear, -1) };
23
- }
24
9
  export function minutesOf(time) {
25
10
  const [h, m] = time.split(":").map(Number);
26
11
  return h * 60 + m;
27
12
  }
28
- /** Whether `date` falls in a window whose open ends are unbounded. */
29
- export function within(date, from, to) {
30
- return (from == null || date >= from) && (to == null || date <= to);
31
- }
@@ -28,7 +28,7 @@ export function computePlan(data) {
28
28
  const charged = bookSalaries(ctx, coaching, booker);
29
29
  booker.flush();
30
30
  bookPayrollTax(ctx);
31
- const mtdcByOffering = new Map(ctx.fyOfferings().map((o) => [o.key, mtdc(ctx, o.key)]));
31
+ const mtdcByOffering = new Map(ctx.data.plan_offerings.map((o) => [o.key, mtdc(ctx, o.key)]));
32
32
  allocatePools(ctx);
33
33
  const bonuses = bookBonuses(ctx);
34
34
  const lines = sumLines(ctx);
@@ -49,7 +49,7 @@ export function computePlan(data) {
49
49
  * products in no area. The catalog stays live, so the database can't check these.
50
50
  */
51
51
  function checkAreas(ctx) {
52
- for (const offering of ctx.fyOfferings()) {
52
+ for (const offering of ctx.data.plan_offerings) {
53
53
  const product = ctx.products.get(offering.product_id);
54
54
  if (product === undefined)
55
55
  continue;
@@ -200,7 +200,7 @@ function summarize(ctx, lines, mtdcByOffering) {
200
200
  ...counts,
201
201
  };
202
202
  };
203
- const offerings = ctx.fyOfferings().sort((a, b) => a.key.localeCompare(b.key));
203
+ const offerings = [...ctx.data.plan_offerings].sort((a, b) => a.key.localeCompare(b.key));
204
204
  const offeringPnl = offerings.map((o) => {
205
205
  const enrollment = ctx.enrollment.get(o.id) ?? 0;
206
206
  const days = ctx.days.get(o.key) ?? 0;
@@ -4,7 +4,7 @@ import type { Id } from "./types.ts";
4
4
  export declare function mtdc(ctx: Context, offeringKey: Id): number;
5
5
  export declare function revenue(ctx: Context, offeringKey: Id): number;
6
6
  /**
7
- * How an amount on a product or program area divides among its plan-year offerings, by MTDC. Null
7
+ * How an amount on a product or program area divides among its offerings, by MTDC. Null
8
8
  * when it has none.
9
9
  */
10
10
  export declare function spreadWeights(ctx: Context, type: "product" | "program_area", key: Id): Map<Id, number> | null;
@@ -19,13 +19,11 @@ export function revenue(ctx, offeringKey) {
19
19
  return sumEntries(ctx, offeringKey, (e) => (ctx.accountType(e.account_id) === "revenue" ? 1 : 0));
20
20
  }
21
21
  /**
22
- * How an amount on a product or program area divides among its plan-year offerings, by MTDC. Null
22
+ * How an amount on a product or program area divides among its offerings, by MTDC. Null
23
23
  * when it has none.
24
24
  */
25
25
  export function spreadWeights(ctx, type, key) {
26
- const offerings = ctx
27
- .fyOfferings()
28
- .filter((o) => type === "product" ? o.product_id === key : ctx.products.get(o.product_id)?.program_area_id === key);
26
+ const offerings = ctx.data.plan_offerings.filter((o) => type === "product" ? o.product_id === key : ctx.products.get(o.product_id)?.program_area_id === key);
29
27
  if (offerings.length === 0)
30
28
  return null;
31
29
  const weights = new Map(offerings.map((o) => [o.key, Math.max(0, mtdc(ctx, o.key))]));
@@ -1,6 +1,6 @@
1
1
  import type { Context } from "./context.ts";
2
2
  /**
3
- * Divides everything in each pool among the plan year's programs' offerings by MTDC, keeping each
3
+ * Divides everything in each pool among the plan's programs' offerings by MTDC, keeping each
4
4
  * amount's account and function. Non-program's net offsets the pools first (cyc-seattle/platform#242):
5
5
  * that part of each pooled amount stays with the organization, so a gain shrinks every program's
6
6
  * share and a loss grows it. Shared costs never count toward MTDC, so one pool's split never shifts
@@ -5,10 +5,7 @@ function isProgram(ctx, productId) {
5
5
  }
6
6
  /** Non-program's net before the pools: its offerings' revenue less their own costs. */
7
7
  function nonProgramNet(ctx) {
8
- const keys = new Set(ctx
9
- .fyOfferings()
10
- .filter((o) => !isProgram(ctx, o.product_id))
11
- .map((o) => o.key));
8
+ const keys = new Set(ctx.data.plan_offerings.filter((o) => !isProgram(ctx, o.product_id)).map((o) => o.key));
12
9
  let net = 0;
13
10
  for (const e of ctx.entries) {
14
11
  if (e.cost_center_type !== "offering" || !keys.has(e.cost_center_key ?? ""))
@@ -22,15 +19,14 @@ function nonProgramNet(ctx) {
22
19
  return net;
23
20
  }
24
21
  /**
25
- * Divides everything in each pool among the plan year's programs' offerings by MTDC, keeping each
22
+ * Divides everything in each pool among the plan's programs' offerings by MTDC, keeping each
26
23
  * amount's account and function. Non-program's net offsets the pools first (cyc-seattle/platform#242):
27
24
  * that part of each pooled amount stays with the organization, so a gain shrinks every program's
28
25
  * share and a loss grows it. Shared costs never count toward MTDC, so one pool's split never shifts
29
26
  * another's.
30
27
  */
31
28
  export function allocatePools(ctx) {
32
- const weights = new Map(ctx
33
- .fyOfferings()
29
+ const weights = new Map(ctx.data.plan_offerings
34
30
  .filter((o) => isProgram(ctx, o.product_id))
35
31
  .map((o) => [o.key, Math.max(0, mtdc(ctx, o.key))]));
36
32
  const total = [...weights.values()].reduce((t, w) => t + w, 0);
@@ -15,7 +15,7 @@ export function countDays(ctx, activities) {
15
15
  * account, a cost.
16
16
  */
17
17
  export function bookRevenue(ctx) {
18
- for (const offering of ctx.fyOfferings()) {
18
+ for (const offering of ctx.data.plan_offerings) {
19
19
  const product = ctx.products.get(offering.product_id);
20
20
  if (product === undefined) {
21
21
  ctx.warn("unknown_product", `Offering ${offering.key} is for a product that doesn't exist`, offering.key);
@@ -405,7 +405,7 @@ export class Staffing {
405
405
  const { ctx } = this;
406
406
  for (const trip of ctx.data.plan_trips) {
407
407
  const offering = ctx.offeringsById.get(trip.offering_id);
408
- if (offering === undefined || !ctx.inYear(offering))
408
+ if (offering === undefined)
409
409
  continue;
410
410
  const travel = (a) => a.kind === "travel" && a.trip?.id === trip.id;
411
411
  const staffed = [...blocks.values()].filter((b) => b.members.some(travel));
@@ -88,7 +88,8 @@ export type ActivityKind = "practice" | "regatta" | "clinic" | "travel";
88
88
  export interface PlanOffering extends Versioned {
89
89
  product_id: Id;
90
90
  name: string;
91
- season_term: string | null;
91
+ /** A `plan_terms` key in the Operations Plan, or none. */
92
+ term_key: Id | null;
92
93
  starts_on: DateString;
93
94
  ends_on: DateString;
94
95
  capacity: number | null;
@@ -247,7 +248,7 @@ export interface PlanBoatRate extends Versioned {
247
248
  /** How many boats of the class the organization has. */
248
249
  quantity: number | null;
249
250
  }
250
- /** A group of shared costs, such as G&A, divided among all of the plan year's offerings by MTDC. */
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 {
252
253
  name: string;
253
254
  }
@@ -281,6 +282,12 @@ 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. */
@@ -321,6 +328,7 @@ export interface PlanData {
321
328
  plan_bonus_owner_products: PlanBonusOwnerProduct[];
322
329
  plan_closed_dates?: PlanClosedDate[];
323
330
  plan_capital_items?: PlanCapitalItem[];
331
+ plan_terms?: PlanTerm[];
324
332
  }
325
333
  /** One line of cost or revenue for the plan year, on one offering, product, pool or the organization. */
326
334
  export interface CostLine {
@@ -57,6 +57,7 @@ export interface Schema {
57
57
  plan_salaried_bookings: PlanSalariedBookings[];
58
58
  plan_salaried_positions: PlanSalariedPositions[];
59
59
  plan_staff_levels: PlanStaffLevels[];
60
+ plan_terms: PlanTerms[];
60
61
  plan_trips: PlanTrips[];
61
62
  plan_versions: PlanVersions[];
62
63
  plans: Plans[];
@@ -706,7 +707,8 @@ export interface PlanOfferings {
706
707
  key: string;
707
708
  product_id: string | Products;
708
709
  name: string;
709
- season_term: string | null;
710
+ /** The term it runs in: copy the Key from a term in the Operations Plan. Leave it empty for an offering in no term. */
711
+ term_key: string | null;
710
712
  starts_on: string;
711
713
  ends_on: string;
712
714
  capacity: number | null;
@@ -971,6 +973,20 @@ export interface PlanStaffLevels {
971
973
  /** In cents: 12500 is $125.00. */
972
974
  hourly_rate_cents: number | null;
973
975
  }
976
+ /** 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. */
977
+ export interface PlanTerms {
978
+ id: string;
979
+ /** The version this row belongs to. */
980
+ version_id: string | PlanVersions;
981
+ /** Stays the same across versions. Copy it where another row asks for this one's Key. */
982
+ key: string;
983
+ /** What it's called, such as Summer 2027. */
984
+ name: string;
985
+ /** Its first day. */
986
+ starts_on: string;
987
+ /** Its last day. */
988
+ ends_on: string;
989
+ }
974
990
  /** A trip an offering travels for, such as an away regatta. */
975
991
  export interface PlanTrips {
976
992
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyc-seattle/platform",
3
- "version": "1.47.0",
3
+ "version": "1.49.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",