@cyc-seattle/platform 1.49.0 → 1.51.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.
- package/README.md +14 -14
- package/dist/planning/bonus.d.ts +9 -7
- package/dist/planning/bonus.js +109 -71
- package/dist/planning/context.d.ts +1 -1
- package/dist/planning/context.js +2 -3
- package/dist/planning/engine.js +41 -18
- package/dist/planning/staffing.d.ts +11 -3
- package/dist/planning/staffing.js +73 -71
- package/dist/planning/terms.d.ts +11 -0
- package/dist/planning/terms.js +22 -0
- package/dist/planning/types.d.ts +52 -36
- package/dist/schema/index.d.ts +97 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,20 +101,20 @@ deployer needs first.
|
|
|
101
101
|
authenticates with Workload Identity Federation and stores no credentials. Each job runs in a
|
|
102
102
|
GitHub environment that holds its identity and state bucket:
|
|
103
103
|
|
|
104
|
-
| Event | Jobs, in order
|
|
105
|
-
| -------------------------- |
|
|
106
|
-
| Pull request | `ci`
|
|
107
|
-
| Merge queue | `ci`, then `dev`: `mise run deploy` as `deploy-dev
|
|
108
|
-
| Push to `main` | `prod-preview`, then `prod`, which waits for the `maintainers` approval
|
|
109
|
-
| Run by hand, from a branch | `ci`, then `dev`
|
|
110
|
-
| Run by hand, from `main` | `prod-preview`, then `prod`
|
|
111
|
-
|
|
112
|
-
Dev is shared, so only the merge queue deploys to it (#69, #80). PRs queued together deploy
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
branch on dev before queueing it, run `gh workflow run pull-request.yml --ref <branch>`.
|
|
104
|
+
| Event | Jobs, in order |
|
|
105
|
+
| -------------------------- | ----------------------------------------------------------------------- |
|
|
106
|
+
| Pull request | `ci` |
|
|
107
|
+
| Merge queue | `ci`, then `dev`: `mise run deploy` as `deploy-dev`; `dev-passed` |
|
|
108
|
+
| Push to `main` | `prod-preview`, then `prod`, which waits for the `maintainers` approval |
|
|
109
|
+
| Run by hand, from a branch | `ci`, then `dev` |
|
|
110
|
+
| Run by hand, from `main` | `prod-preview`, then `prod` |
|
|
111
|
+
|
|
112
|
+
Dev is shared, so only the merge queue deploys to it (#69, #80). PRs queued together deploy as
|
|
113
|
+
one batch: each queued PR's run skips dev when a newer PR is queued behind it, and the newest
|
|
114
|
+
deploys them all. The queue requires `dev-passed`, so a batch merges only once its dev deploy passes, and `main` gets the exact commit the queue tested, in one push. If the
|
|
115
|
+
newest PR fails, the queue drops it and the rest of the batch deploys without it. A queued PR of only docs or CI skips `dev`. The queue
|
|
116
|
+
doesn't run the integration tests (#161); a run by hand does. To try a
|
|
117
|
+
branch on dev and test it before queueing it, run `gh workflow run pull-request.yml --ref <branch>`.
|
|
118
118
|
|
|
119
119
|
`prod-preview` runs `mise run preview` as `deploy-runner` and writes the diff to the job summary.
|
|
120
120
|
`prod` runs `mise run deploy` on the same commit once approved. It runs only when the preview shows
|
package/dist/planning/bonus.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { Context } from "./context.ts";
|
|
2
|
-
import type
|
|
2
|
+
import { type OfferingTerms } from "./terms.ts";
|
|
3
|
+
import type { BonusSummary } from "./types.ts";
|
|
3
4
|
/**
|
|
4
|
-
* Records each bonus owner's `bonus_rate` × its products' net
|
|
5
|
-
* pools or before them.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Records each bonus owner's `bonus_rate` × its products' net in each term, above zero, after the
|
|
6
|
+
* pools or before them. A term's loss doesn't lower another term's bonus. Offerings in no term, and
|
|
7
|
+
* anything not on an offering, make one more base, for the year. Every base is worked out before any
|
|
8
|
+
* bonus is booked, so one bonus never counts in another's base. Each bonus is divided among that
|
|
9
|
+
* base's offerings by revenue, with the year's payroll tax on it, and neither is counted in MTDC.
|
|
10
|
+
* Returns each bonus, by owner and term.
|
|
9
11
|
*/
|
|
10
|
-
export declare function bookBonuses(ctx: Context):
|
|
12
|
+
export declare function bookBonuses(ctx: Context, terms: OfferingTerms): BonusSummary[];
|
package/dist/planning/bonus.js
CHANGED
|
@@ -1,77 +1,99 @@
|
|
|
1
1
|
import { revenue } from "./measures.js";
|
|
2
|
+
import { compareTerms } from "./terms.js";
|
|
2
3
|
/**
|
|
3
|
-
* Records each bonus owner's `bonus_rate` × its products' net
|
|
4
|
-
* pools or before them.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Records each bonus owner's `bonus_rate` × its products' net in each term, above zero, after the
|
|
5
|
+
* pools or before them. A term's loss doesn't lower another term's bonus. Offerings in no term, and
|
|
6
|
+
* anything not on an offering, make one more base, for the year. Every base is worked out before any
|
|
7
|
+
* bonus is booked, so one bonus never counts in another's base. Each bonus is divided among that
|
|
8
|
+
* base's offerings by revenue, with the year's payroll tax on it, and neither is counted in MTDC.
|
|
9
|
+
* Returns each bonus, by owner and term.
|
|
8
10
|
*/
|
|
9
|
-
export function bookBonuses(ctx) {
|
|
10
|
-
const bonuses = ctx.data.plan_bonus_owners.map((owner) => ({ owner,
|
|
11
|
+
export function bookBonuses(ctx, terms) {
|
|
12
|
+
const bonuses = ctx.data.plan_bonus_owners.map((owner) => ({ owner, bases: basesOf(ctx, owner, terms) }));
|
|
11
13
|
const taxRate = ctx.rates?.payroll_tax_rate ?? 0;
|
|
12
14
|
const taxAccount = taxRate > 0 && bonuses.length > 0 ? ctx.costAccount("payroll_tax") : undefined;
|
|
13
|
-
const
|
|
14
|
-
for (const { owner,
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
// Its net came from elsewhere, such as revenue booked on a product, so its offerings share it evenly.
|
|
30
|
-
ctx.warn("bonus_without_revenue", `${owner.role_name}'s offerings have no revenue of their own, so its bonus is split evenly among them`, owner.key);
|
|
31
|
-
for (const key of base.offeringKeys)
|
|
32
|
-
weights.set(key, 1);
|
|
33
|
-
}
|
|
34
|
-
const total = [...weights.values()].reduce((t, w) => t + w, 0);
|
|
35
|
-
if (owner.position_key != null) {
|
|
36
|
-
byPosition.set(owner.position_key, (byPosition.get(owner.position_key) ?? 0) + bonus);
|
|
37
|
-
}
|
|
38
|
-
for (const [key, w] of weights) {
|
|
39
|
-
const amount = (bonus * w) / total;
|
|
40
|
-
ctx.book({
|
|
41
|
-
cost_center_type: "offering",
|
|
42
|
-
cost_center_key: key,
|
|
43
|
-
account_id: owner.account_id,
|
|
44
|
-
function: ctx.functionFor(owner.account_id, null, "offering"),
|
|
45
|
-
amount,
|
|
46
|
-
line_key: `bonus:${owner.key}`,
|
|
47
|
-
line_name: `Bonus: ${owner.role_name}`,
|
|
48
|
-
explanation: `${owner.role_name}: ${owner.bonus_rate} × net of its products`,
|
|
49
|
-
stage: "bonus",
|
|
50
|
-
mtdc_share: 0,
|
|
51
|
-
});
|
|
52
|
-
if (taxAccount === undefined)
|
|
53
|
-
continue;
|
|
54
|
-
ctx.book({
|
|
55
|
-
cost_center_type: "offering",
|
|
56
|
-
cost_center_key: key,
|
|
57
|
-
account_id: taxAccount,
|
|
58
|
-
function: ctx.functionFor(owner.account_id, null, "offering"),
|
|
59
|
-
amount: amount * taxRate,
|
|
60
|
-
line_key: `bonus_payroll_tax:${owner.key}`,
|
|
61
|
-
line_name: `Payroll tax on bonus: ${owner.role_name}`,
|
|
62
|
-
explanation: `${taxRate} × ${owner.role_name}'s bonus`,
|
|
63
|
-
stage: "bonus",
|
|
64
|
-
mtdc_share: 0,
|
|
15
|
+
const summaries = [];
|
|
16
|
+
for (const { owner, bases } of bonuses) {
|
|
17
|
+
const area = areaOf(ctx, owner);
|
|
18
|
+
for (const base of bases) {
|
|
19
|
+
const bonus = owner.bonus_rate * Math.max(0, base.net);
|
|
20
|
+
const booked = bonus === 0 ? 0 : bookBonus(ctx, owner, base, bonus, taxRate, taxAccount);
|
|
21
|
+
summaries.push({
|
|
22
|
+
bonus_owner_key: owner.key,
|
|
23
|
+
role_name: owner.role_name,
|
|
24
|
+
position_key: owner.position_key ?? null,
|
|
25
|
+
program_area_id: area,
|
|
26
|
+
term_key: base.term?.key ?? null,
|
|
27
|
+
bonus_rate: owner.bonus_rate,
|
|
28
|
+
net_cents: Math.round(base.net),
|
|
29
|
+
bonus_cents: Math.round(booked),
|
|
65
30
|
});
|
|
66
31
|
}
|
|
67
32
|
}
|
|
68
|
-
return
|
|
33
|
+
return summaries;
|
|
34
|
+
}
|
|
35
|
+
/** Books one term's bonus on its offerings, by revenue. Returns what it booked. */
|
|
36
|
+
function bookBonus(ctx, owner, base, bonus, taxRate, taxAccount) {
|
|
37
|
+
const weights = new Map();
|
|
38
|
+
for (const key of base.offeringKeys) {
|
|
39
|
+
const r = revenue(ctx, key);
|
|
40
|
+
if (r > 0)
|
|
41
|
+
weights.set(key, r);
|
|
42
|
+
}
|
|
43
|
+
if (weights.size === 0) {
|
|
44
|
+
if (base.offeringKeys.length === 0) {
|
|
45
|
+
ctx.warn("bonus_without_offerings", `${owner.role_name}'s bonus has no offerings this year to charge it to, so it's left out`, owner.key);
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
// Its net came from elsewhere, such as revenue booked on a product, so its offerings share it evenly.
|
|
49
|
+
ctx.warn("bonus_without_revenue", `${owner.role_name}'s offerings have no revenue of their own, so its bonus is split evenly among them`, owner.key);
|
|
50
|
+
for (const key of base.offeringKeys)
|
|
51
|
+
weights.set(key, 1);
|
|
52
|
+
}
|
|
53
|
+
const total = [...weights.values()].reduce((t, w) => t + w, 0);
|
|
54
|
+
for (const [key, w] of weights) {
|
|
55
|
+
const amount = (bonus * w) / total;
|
|
56
|
+
ctx.book({
|
|
57
|
+
cost_center_type: "offering",
|
|
58
|
+
cost_center_key: key,
|
|
59
|
+
account_id: owner.account_id,
|
|
60
|
+
function: ctx.functionFor(owner.account_id, null, "offering"),
|
|
61
|
+
amount,
|
|
62
|
+
line_key: `bonus:${owner.key}`,
|
|
63
|
+
line_name: `Bonus: ${owner.role_name}`,
|
|
64
|
+
explanation: `${owner.role_name}: ${owner.bonus_rate} × net of its products ${base.term === null ? "in no term" : `in ${base.term.name}`}`,
|
|
65
|
+
stage: "bonus",
|
|
66
|
+
mtdc_share: 0,
|
|
67
|
+
});
|
|
68
|
+
if (taxAccount === undefined)
|
|
69
|
+
continue;
|
|
70
|
+
ctx.book({
|
|
71
|
+
cost_center_type: "offering",
|
|
72
|
+
cost_center_key: key,
|
|
73
|
+
account_id: taxAccount,
|
|
74
|
+
function: ctx.functionFor(owner.account_id, null, "offering"),
|
|
75
|
+
amount: amount * taxRate,
|
|
76
|
+
line_key: `bonus_payroll_tax:${owner.key}`,
|
|
77
|
+
line_name: `Payroll tax on bonus: ${owner.role_name}`,
|
|
78
|
+
explanation: `${taxRate} × ${owner.role_name}'s bonus`,
|
|
79
|
+
stage: "bonus",
|
|
80
|
+
mtdc_share: 0,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return bonus;
|
|
84
|
+
}
|
|
85
|
+
/** The program area of all the owner's products, or null when they're in more than one or none. */
|
|
86
|
+
function areaOf(ctx, owner) {
|
|
87
|
+
const areas = new Set(ctx.data.plan_bonus_owner_products
|
|
88
|
+
.filter((row) => row.bonus_owner_id === owner.id)
|
|
89
|
+
.map((row) => ctx.products.get(row.product_id)?.program_area_id ?? null));
|
|
90
|
+
return areas.size === 1 ? ([...areas][0] ?? null) : null;
|
|
69
91
|
}
|
|
70
92
|
/**
|
|
71
|
-
* The owner's products' net
|
|
72
|
-
* area's products, and warns about any other.
|
|
93
|
+
* The owner's products' net in each term they have an offering in, with those offerings, in term
|
|
94
|
+
* order and then no term. An area plan's bonus counts only that area's products, and warns about any other.
|
|
73
95
|
*/
|
|
74
|
-
function
|
|
96
|
+
function basesOf(ctx, owner, terms) {
|
|
75
97
|
const plan = ctx.planOfVersion.get(owner.version_id);
|
|
76
98
|
const products = new Set();
|
|
77
99
|
for (const row of ctx.data.plan_bonus_owner_products) {
|
|
@@ -84,21 +106,37 @@ function baseOf(ctx, owner) {
|
|
|
84
106
|
}
|
|
85
107
|
products.add(row.product_id);
|
|
86
108
|
}
|
|
87
|
-
const
|
|
109
|
+
const bases = new Map();
|
|
110
|
+
const baseFor = (term) => {
|
|
111
|
+
const base = bases.get(term?.key ?? null) ?? { term, net: 0, offeringKeys: [] };
|
|
112
|
+
bases.set(term?.key ?? null, base);
|
|
113
|
+
return base;
|
|
114
|
+
};
|
|
115
|
+
const owned = new Set();
|
|
116
|
+
for (const o of ctx.data.plan_offerings) {
|
|
117
|
+
if (!products.has(o.product_id))
|
|
118
|
+
continue;
|
|
119
|
+
owned.add(o.key);
|
|
120
|
+
baseFor(terms.get(o.key) ?? null).offeringKeys.push(o.key);
|
|
121
|
+
}
|
|
88
122
|
const afterPools = (owner.basis ?? "after_shared_costs") === "after_shared_costs";
|
|
89
|
-
let net = 0;
|
|
90
123
|
for (const e of ctx.entries) {
|
|
91
124
|
if (e.stage === "bonus" || (!afterPools && e.stage === "pool") || e.cost_center_key == null)
|
|
92
125
|
continue;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
126
|
+
let term;
|
|
127
|
+
if (e.cost_center_type === "offering" && owned.has(e.cost_center_key)) {
|
|
128
|
+
term = terms.get(e.cost_center_key) ?? null;
|
|
129
|
+
}
|
|
130
|
+
else if (e.cost_center_type === "product" && products.has(e.cost_center_key)) {
|
|
131
|
+
term = null;
|
|
132
|
+
}
|
|
133
|
+
else
|
|
96
134
|
continue;
|
|
97
135
|
const type = ctx.accountType(e.account_id);
|
|
98
136
|
if (type === "revenue")
|
|
99
|
-
net += e.amount;
|
|
137
|
+
baseFor(term).net += e.amount;
|
|
100
138
|
else if (type === "expense")
|
|
101
|
-
net -= e.amount;
|
|
139
|
+
baseFor(term).net -= e.amount;
|
|
102
140
|
}
|
|
103
|
-
return
|
|
141
|
+
return [...bases.values()].sort((a, b) => compareTerms(a.term, b.term));
|
|
104
142
|
}
|
|
@@ -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
|
|
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;
|
package/dist/planning/context.js
CHANGED
|
@@ -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: "
|
|
14
|
+
ribs: "coach boats",
|
|
16
15
|
boat_maintenance: "boat maintenance",
|
|
17
|
-
resources: "
|
|
16
|
+
resources: "boat charters",
|
|
18
17
|
travel: "travel",
|
|
19
18
|
cogs: "cost of goods sold",
|
|
20
19
|
};
|
package/dist/planning/engine.js
CHANGED
|
@@ -8,6 +8,7 @@ import { bookPayrollTax } from "./payroll.js";
|
|
|
8
8
|
import { allocatePools } from "./pools.js";
|
|
9
9
|
import { bookRevenue, countDays } from "./revenue.js";
|
|
10
10
|
import { bookSalaries } from "./salaried.js";
|
|
11
|
+
import { compareTerms, offeringTerms } from "./terms.js";
|
|
11
12
|
import { enrollmentOf, round2, Staffing } from "./staffing.js";
|
|
12
13
|
/**
|
|
13
14
|
* Calculates the plan year's P&L from a plan's rows. The steps run in the order
|
|
@@ -18,6 +19,7 @@ export function computePlan(data) {
|
|
|
18
19
|
for (const o of data.plan_offerings)
|
|
19
20
|
ctx.enrollment.set(o.id, enrollmentOf(o));
|
|
20
21
|
checkAreas(ctx);
|
|
22
|
+
const terms = offeringTerms(ctx);
|
|
21
23
|
const { activities, coaching, doubleBooked } = new Staffing(ctx).run(scheduleActivities(ctx));
|
|
22
24
|
countDays(ctx, activities);
|
|
23
25
|
bookRevenue(ctx);
|
|
@@ -30,15 +32,22 @@ export function computePlan(data) {
|
|
|
30
32
|
bookPayrollTax(ctx);
|
|
31
33
|
const mtdcByOffering = new Map(ctx.data.plan_offerings.map((o) => [o.key, mtdc(ctx, o.key)]));
|
|
32
34
|
allocatePools(ctx);
|
|
33
|
-
const bonuses = bookBonuses(ctx);
|
|
35
|
+
const bonuses = bookBonuses(ctx, terms);
|
|
36
|
+
const bonusByPosition = new Map();
|
|
37
|
+
for (const b of bonuses) {
|
|
38
|
+
if (b.position_key == null)
|
|
39
|
+
continue;
|
|
40
|
+
bonusByPosition.set(b.position_key, (bonusByPosition.get(b.position_key) ?? 0) + b.bonus_cents);
|
|
41
|
+
}
|
|
34
42
|
const lines = sumLines(ctx);
|
|
35
43
|
const organization = organizationTotals(ctx, lines);
|
|
36
44
|
return {
|
|
37
45
|
lines,
|
|
38
|
-
pnl: summarize(ctx, lines, mtdcByOffering),
|
|
46
|
+
pnl: summarize(ctx, lines, mtdcByOffering, terms),
|
|
39
47
|
organization,
|
|
40
48
|
activities,
|
|
41
|
-
positions: summarizePositions(ctx, coaching, charged,
|
|
49
|
+
positions: summarizePositions(ctx, coaching, charged, bonusByPosition, doubleBooked),
|
|
50
|
+
bonuses,
|
|
42
51
|
capital: rankCapital(ctx, lines, organization),
|
|
43
52
|
warnings: ctx.warnings,
|
|
44
53
|
};
|
|
@@ -173,8 +182,8 @@ function sumLines(ctx) {
|
|
|
173
182
|
})
|
|
174
183
|
.filter((l) => l.amount_cents !== 0);
|
|
175
184
|
}
|
|
176
|
-
/** Each plan-year offering's P&L, then each product's, then each program area's. */
|
|
177
|
-
function summarize(ctx, lines, mtdcByOffering) {
|
|
185
|
+
/** Each plan-year offering's P&L, then each product's, then each program area's, then each area's terms. */
|
|
186
|
+
function summarize(ctx, lines, mtdcByOffering, terms) {
|
|
178
187
|
const pnl = (level, key, own, counts) => {
|
|
179
188
|
const byAccount = new Map();
|
|
180
189
|
let revenue = 0;
|
|
@@ -231,24 +240,38 @@ function summarize(ctx, lines, mtdcByOffering) {
|
|
|
231
240
|
for (const l of lines)
|
|
232
241
|
if (l.cost_center_type === "program_area" && l.cost_center_key !== null)
|
|
233
242
|
areaIds.add(l.cost_center_key);
|
|
234
|
-
const areaPnl = [
|
|
243
|
+
const areaPnl = [];
|
|
244
|
+
const termPnl = [];
|
|
245
|
+
for (const id of [...areaIds].sort()) {
|
|
235
246
|
const own = offeringPnl.filter((s) => {
|
|
236
247
|
const product = ctx.offeringsByKey.get(s.key)?.product_id;
|
|
237
248
|
return product !== undefined && ctx.products.get(product)?.program_area_id === id;
|
|
238
249
|
});
|
|
239
|
-
const keys = new Set(own.map((s) => s.key));
|
|
240
250
|
const products = new Set(own.map((s) => ctx.offeringsByKey.get(s.key)?.product_id));
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
251
|
+
// Costs left on the area or a product, because it had no offerings to divide them among, are in no term.
|
|
252
|
+
const rollup = (level, offerings, leftovers) => {
|
|
253
|
+
const keys = new Set(offerings.map((s) => s.key));
|
|
254
|
+
const add = (field) => offerings.reduce((t, s) => t + s[field], 0);
|
|
255
|
+
return pnl(level, id, lines.filter((l) => (l.cost_center_type === "offering" && l.cost_center_key !== null && keys.has(l.cost_center_key)) ||
|
|
256
|
+
(leftovers && l.cost_center_type === "product" && products.has(l.cost_center_key ?? undefined)) ||
|
|
257
|
+
(leftovers && l.cost_center_type === "program_area" && l.cost_center_key === id)), {
|
|
258
|
+
mtdc_cents: add("mtdc_cents"),
|
|
259
|
+
enrollment: add("enrollment"),
|
|
260
|
+
days: add("days"),
|
|
261
|
+
sailor_days: add("sailor_days"),
|
|
262
|
+
});
|
|
263
|
+
};
|
|
264
|
+
areaPnl.push(rollup("program_area", own, true));
|
|
265
|
+
const termOf = (s) => terms.get(s.key) ?? null;
|
|
266
|
+
const areaTerms = [...new Map(own.map((s) => [termOf(s)?.key, termOf(s)])).values()].filter((t) => t !== null);
|
|
267
|
+
for (const term of [...areaTerms.sort(compareTerms), null]) {
|
|
268
|
+
const inTerm = own.filter((s) => termOf(s)?.key === term?.key);
|
|
269
|
+
const row = { ...rollup("program_area_term", inTerm, term === null), term_key: term?.key ?? null };
|
|
270
|
+
if (inTerm.length > 0 || row.by_account.length > 0)
|
|
271
|
+
termPnl.push(row);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return [...offeringPnl, ...productPnl, ...areaPnl, ...termPnl];
|
|
252
275
|
}
|
|
253
276
|
function divide(totals, by) {
|
|
254
277
|
if (by <= 0)
|
|
@@ -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
|
-
*
|
|
48
|
-
* blocks
|
|
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
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
|
189
|
-
*
|
|
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
|
|
196
|
-
const
|
|
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
|
-
? `${
|
|
208
|
-
: `${
|
|
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
|
-
|
|
219
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Context } from "./context.ts";
|
|
2
|
+
import type { Id, PlanTerm } from "./types.ts";
|
|
3
|
+
/** Each offering's term, by offering key, in no particular order. Null for an offering in none. */
|
|
4
|
+
export type OfferingTerms = Map<Id, PlanTerm | null>;
|
|
5
|
+
/**
|
|
6
|
+
* Finds each offering's term in the plan's terms. An offering whose term isn't there, such as one deleted
|
|
7
|
+
* from the Operations Plan, is in none, with a warning.
|
|
8
|
+
*/
|
|
9
|
+
export declare function offeringTerms(ctx: Context): OfferingTerms;
|
|
10
|
+
/** Orders terms by their first days, then names, with no term (null) last. */
|
|
11
|
+
export declare function compareTerms(a: PlanTerm | null, b: PlanTerm | null): number;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finds each offering's term in the plan's terms. An offering whose term isn't there, such as one deleted
|
|
3
|
+
* from the Operations Plan, is in none, with a warning.
|
|
4
|
+
*/
|
|
5
|
+
export function offeringTerms(ctx) {
|
|
6
|
+
const terms = new Map((ctx.data.plan_terms ?? []).map((t) => [t.key, t]));
|
|
7
|
+
const byOffering = new Map();
|
|
8
|
+
for (const o of ctx.data.plan_offerings) {
|
|
9
|
+
const term = o.term_key == null ? null : (terms.get(o.term_key) ?? null);
|
|
10
|
+
if (o.term_key != null && term === null) {
|
|
11
|
+
ctx.warn("unknown_term", `${o.name} is in a term the Operations Plan doesn't have, so it counts as in no term`, o.key);
|
|
12
|
+
}
|
|
13
|
+
byOffering.set(o.key, term);
|
|
14
|
+
}
|
|
15
|
+
return byOffering;
|
|
16
|
+
}
|
|
17
|
+
/** Orders terms by their first days, then names, with no term (null) last. */
|
|
18
|
+
export function compareTerms(a, b) {
|
|
19
|
+
if (a === null || b === null)
|
|
20
|
+
return a === b ? 0 : a === null ? 1 : -1;
|
|
21
|
+
return a.starts_on.localeCompare(b.starts_on) || a.name.localeCompare(b.name) || a.key.localeCompare(b.key);
|
|
22
|
+
}
|
package/dist/planning/types.d.ts
CHANGED
|
@@ -52,12 +52,27 @@ export interface QualificationCover {
|
|
|
52
52
|
export interface BoatClass {
|
|
53
53
|
id: Id;
|
|
54
54
|
name: string;
|
|
55
|
-
|
|
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
|
-
|
|
61
|
+
/** Something the organization owns: a sailboat, a powerboat, a motor, or other equipment. */
|
|
62
|
+
export interface Asset {
|
|
59
63
|
id: Id;
|
|
60
|
-
|
|
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;
|
|
@@ -134,7 +149,7 @@ export interface PlanActivity extends Versioned {
|
|
|
134
149
|
/** The block it's staffed in, if any. */
|
|
135
150
|
block_id: Id | null;
|
|
136
151
|
}
|
|
137
|
-
/** Activities on one date that share their staff
|
|
152
|
+
/** Activities on one date that share their staff and boats. */
|
|
138
153
|
export interface PlanActivityBlock extends Versioned {
|
|
139
154
|
date: DateString;
|
|
140
155
|
name: string;
|
|
@@ -150,21 +165,13 @@ export interface PlanBlockStaff extends Versioned {
|
|
|
150
165
|
/** How a salaried position is paid for the block: out of its salary (the default), or by the hour at its own rate. */
|
|
151
166
|
pay: "salary" | "hourly" | null;
|
|
152
167
|
}
|
|
153
|
-
/**
|
|
154
|
-
export interface PlanBlockResource extends Versioned {
|
|
155
|
-
block_id: Id;
|
|
156
|
-
/** A `plan_resources` key. */
|
|
157
|
-
resource_key: Id;
|
|
158
|
-
quantity: number;
|
|
159
|
-
/** Hours each is used; empty for the block's time on the water (RIBs) or its paid time (the rest). */
|
|
160
|
-
hours: number | null;
|
|
161
|
-
}
|
|
162
|
-
/** 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. */
|
|
163
169
|
export interface PlanBlockBoats extends Versioned {
|
|
164
170
|
block_id: Id;
|
|
165
171
|
boat_class_id: Id;
|
|
166
172
|
count: number;
|
|
167
173
|
sailor_owned: number | null;
|
|
174
|
+
chartered?: number | null;
|
|
168
175
|
}
|
|
169
176
|
export interface PlanLaborLine extends Versioned {
|
|
170
177
|
name: string;
|
|
@@ -231,22 +238,15 @@ export interface PlanSalariedBooking extends Versioned {
|
|
|
231
238
|
kind: "management";
|
|
232
239
|
hours: number;
|
|
233
240
|
}
|
|
234
|
-
|
|
235
|
-
resource_kind_id: Id;
|
|
236
|
-
name: string;
|
|
237
|
-
quantity: number | null;
|
|
238
|
-
cost_per_hour_cents: number | null;
|
|
239
|
-
cost_per_day_cents: number | null;
|
|
240
|
-
fuel_per_hour_cents: number | null;
|
|
241
|
-
maintenance_per_hour_cents: number | null;
|
|
242
|
-
/** Set for a rental, whose cost leaves MTDC. */
|
|
243
|
-
excluded_from_mtdc: boolean;
|
|
244
|
-
}
|
|
241
|
+
/** A boat class's rates for the year. */
|
|
245
242
|
export interface PlanBoatRate extends Versioned {
|
|
246
243
|
boat_class_id: Id;
|
|
244
|
+
/** Upkeep for each hour one of the organization's boats of the class is on the water. */
|
|
247
245
|
maintenance_per_boat_hour_cents: number;
|
|
248
|
-
/**
|
|
249
|
-
|
|
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;
|
|
250
250
|
}
|
|
251
251
|
/** A group of shared costs, such as G&A, divided among all of the plan's offerings by MTDC. */
|
|
252
252
|
export interface PlanPool extends Versioned {
|
|
@@ -294,15 +294,15 @@ export interface PlanData {
|
|
|
294
294
|
fiscal_year: number;
|
|
295
295
|
organization: Organization;
|
|
296
296
|
organization_cost_accounts: OrganizationCostAccount[];
|
|
297
|
-
/** The resource kind whose `plan_resources` price RIB hours. */
|
|
298
|
-
rib_resource_kind_id: Id | null;
|
|
299
297
|
products: Product[];
|
|
300
298
|
program_areas: ProgramArea[];
|
|
301
299
|
accounts: Account[];
|
|
302
300
|
qualifications: Qualification[];
|
|
303
301
|
qualification_covers: QualificationCover[];
|
|
304
302
|
boat_classes: BoatClass[];
|
|
305
|
-
|
|
303
|
+
/** The asset list, with its boats, which the engine counts by class on each day. */
|
|
304
|
+
assets: Asset[];
|
|
305
|
+
boats: Boat[];
|
|
306
306
|
/** The plans and versions being calculated, so the engine can tell which plan each offering is in. */
|
|
307
307
|
plans: Plan[];
|
|
308
308
|
plan_versions: PlanVersion[];
|
|
@@ -312,7 +312,6 @@ export interface PlanData {
|
|
|
312
312
|
plan_activities: PlanActivity[];
|
|
313
313
|
plan_activity_blocks: PlanActivityBlock[];
|
|
314
314
|
plan_block_staff: PlanBlockStaff[];
|
|
315
|
-
plan_block_resources: PlanBlockResource[];
|
|
316
315
|
plan_block_boats: PlanBlockBoats[];
|
|
317
316
|
plan_labor_lines: PlanLaborLine[];
|
|
318
317
|
plan_budget_lines: PlanBudgetLine[];
|
|
@@ -321,7 +320,6 @@ export interface PlanData {
|
|
|
321
320
|
plan_staff_levels: PlanStaffLevel[];
|
|
322
321
|
plan_salaried_positions: PlanSalariedPosition[];
|
|
323
322
|
plan_salaried_bookings: PlanSalariedBooking[];
|
|
324
|
-
plan_resources: PlanResource[];
|
|
325
323
|
plan_boat_rates: PlanBoatRate[];
|
|
326
324
|
plan_pools: PlanPool[];
|
|
327
325
|
plan_bonus_owners: PlanBonusOwner[];
|
|
@@ -351,9 +349,12 @@ export interface Totals {
|
|
|
351
349
|
}
|
|
352
350
|
/** One offering's P&L for the plan year, or a product's or program area's, which adds up its offerings. */
|
|
353
351
|
export interface Pnl extends Totals {
|
|
354
|
-
|
|
352
|
+
/** `program_area_term` is a program area's offerings in one term, or in none. */
|
|
353
|
+
level: "offering" | "product" | "program_area" | "program_area_term";
|
|
355
354
|
/** An offering key, a product id, or a program area id. */
|
|
356
355
|
key: Id;
|
|
356
|
+
/** A `program_area_term`'s `plan_terms` key, null for its offerings in no term. Only that level has one. */
|
|
357
|
+
term_key?: Id | null;
|
|
357
358
|
/** Each account's total, revenue and expense alike. */
|
|
358
359
|
by_account: {
|
|
359
360
|
account_id: Id;
|
|
@@ -378,9 +379,8 @@ export interface ComputedActivity {
|
|
|
378
379
|
paid_minutes: number;
|
|
379
380
|
on_water_minutes: number;
|
|
380
381
|
uses_boats: boolean;
|
|
381
|
-
/** Its share of its block's staff,
|
|
382
|
+
/** Its share of its block's staff and boats, the organization's and chartered, by its sailors. */
|
|
382
383
|
coaches: number;
|
|
383
|
-
ribs: number;
|
|
384
384
|
boats: number;
|
|
385
385
|
block_id: Id | null;
|
|
386
386
|
}
|
|
@@ -450,10 +450,26 @@ export interface PositionSummary {
|
|
|
450
450
|
/** Dates it's on two or more groups at once. */
|
|
451
451
|
double_booked_dates: number;
|
|
452
452
|
}
|
|
453
|
+
/** A bonus owner's bonus for one term, or on its offerings in no term for the year. */
|
|
454
|
+
export interface BonusSummary {
|
|
455
|
+
bonus_owner_key: Id;
|
|
456
|
+
role_name: string;
|
|
457
|
+
position_key: Id | null;
|
|
458
|
+
/** The program area of all the owner's products, or null when they're in more than one. */
|
|
459
|
+
program_area_id: Id | null;
|
|
460
|
+
/** A `plan_terms` key, or null for its offerings in no term. */
|
|
461
|
+
term_key: Id | null;
|
|
462
|
+
bonus_rate: number;
|
|
463
|
+
/** The net the bonus is figured on, which can be below zero. */
|
|
464
|
+
net_cents: number;
|
|
465
|
+
bonus_cents: number;
|
|
466
|
+
}
|
|
453
467
|
export interface EngineOutput {
|
|
454
468
|
lines: CostLine[];
|
|
455
|
-
/** Each offering's P&L, then each product's, then each program area's. */
|
|
469
|
+
/** Each offering's P&L, then each product's, then each program area's, then each area's terms. */
|
|
456
470
|
pnl: Pnl[];
|
|
471
|
+
/** Each bonus owner's bonus in each term it has offerings in, then in no term. */
|
|
472
|
+
bonuses: BonusSummary[];
|
|
457
473
|
organization: OrganizationTotals;
|
|
458
474
|
activities: ComputedActivity[];
|
|
459
475
|
/** Each salaried position's pay for the year. */
|
package/dist/schema/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/** Every collection, for `createDirectus<Schema>()`. */
|
|
2
2
|
export interface Schema {
|
|
3
3
|
accounts: Accounts[];
|
|
4
|
+
announcements: Announcements[];
|
|
5
|
+
assets: Assets[];
|
|
4
6
|
audit_findings: AuditFindings[];
|
|
5
7
|
boat_classes: BoatClasses[];
|
|
6
8
|
boats: Boats[];
|
|
@@ -25,6 +27,7 @@ export interface Schema {
|
|
|
25
27
|
group_policies: GroupPolicies[];
|
|
26
28
|
groups: Groups[];
|
|
27
29
|
households: Households[];
|
|
30
|
+
links: Links[];
|
|
28
31
|
medical_profiles: MedicalProfiles[];
|
|
29
32
|
offerings: Offerings[];
|
|
30
33
|
organization_cost_accounts: OrganizationCostAccounts[];
|
|
@@ -33,7 +36,6 @@ export interface Schema {
|
|
|
33
36
|
plan_activities: PlanActivities[];
|
|
34
37
|
plan_activity_blocks: PlanActivityBlocks[];
|
|
35
38
|
plan_block_boats: PlanBlockBoats[];
|
|
36
|
-
plan_block_resources: PlanBlockResources[];
|
|
37
39
|
plan_block_staff: PlanBlockStaff[];
|
|
38
40
|
plan_boat_rates: PlanBoatRates[];
|
|
39
41
|
plan_bonus_owner_products: PlanBonusOwnerProducts[];
|
|
@@ -46,7 +48,7 @@ export interface Schema {
|
|
|
46
48
|
plan_pools: PlanPools[];
|
|
47
49
|
plan_programs: PlanPrograms[];
|
|
48
50
|
plan_rates: PlanRates[];
|
|
49
|
-
|
|
51
|
+
plan_result_bonuses: PlanResultBonuses[];
|
|
50
52
|
plan_result_capital: PlanResultCapital[];
|
|
51
53
|
plan_result_lines: PlanResultLines[];
|
|
52
54
|
plan_result_pnl: PlanResultPnl[];
|
|
@@ -70,7 +72,6 @@ export interface Schema {
|
|
|
70
72
|
qualification_covers: QualificationCovers[];
|
|
71
73
|
qualifications: Qualifications[];
|
|
72
74
|
registrations: Registrations[];
|
|
73
|
-
resource_kinds: ResourceKinds[];
|
|
74
75
|
schools: Schools[];
|
|
75
76
|
sync_leases: SyncLeases[];
|
|
76
77
|
sync_runs: SyncRuns[];
|
|
@@ -115,6 +116,39 @@ export interface Accounts {
|
|
|
115
116
|
/** Kept for history, and closed to new lines. */
|
|
116
117
|
retired: boolean;
|
|
117
118
|
}
|
|
119
|
+
/** What Sail Team's home page shows, newest first. */
|
|
120
|
+
export interface Announcements {
|
|
121
|
+
id: string;
|
|
122
|
+
title: string;
|
|
123
|
+
body: string;
|
|
124
|
+
/** The date the home page shows. Newest comes first. */
|
|
125
|
+
published_on: string;
|
|
126
|
+
}
|
|
127
|
+
/** 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. */
|
|
128
|
+
export interface Assets {
|
|
129
|
+
id: string;
|
|
130
|
+
/** A sailboat or powerboat also needs a row in Boats. Directus can't check that, so keep them matching. */
|
|
131
|
+
kind: "sailboat" | "powerboat" | "motor" | "other";
|
|
132
|
+
/** Its name, if it has one, such as Scooter or the van. */
|
|
133
|
+
name: string | null;
|
|
134
|
+
/** This asset's boat details, if it is a sailboat or powerboat. */
|
|
135
|
+
boats: (string | Boats)[];
|
|
136
|
+
/** Who made it, such as Zodiac or Yamaha. */
|
|
137
|
+
make: string | null;
|
|
138
|
+
/** Its model. A boat's model is its boat class, in Boats. */
|
|
139
|
+
model: string | null;
|
|
140
|
+
/** The number that identifies it: a boat's hull number (HIN), a vehicle's VIN, or a serial number. */
|
|
141
|
+
identifier: string | null;
|
|
142
|
+
/** The state registration number, such as WN 1234 AB, if it has one. */
|
|
143
|
+
registration_number: string | null;
|
|
144
|
+
/** Its book value, as in QuickBooks account 1500. In cents: 12500 is $125.00. */
|
|
145
|
+
value_cents: number | null;
|
|
146
|
+
/** The day CSC got it, or expects to. Planning counts it from this day; empty counts it from the start. */
|
|
147
|
+
acquired_on: string | null;
|
|
148
|
+
/** The day CSC sold, scrapped, or gave it away. Planning stops counting it on this day. Empty while CSC still has it. */
|
|
149
|
+
disposed_on: string | null;
|
|
150
|
+
note: string | null;
|
|
151
|
+
}
|
|
118
152
|
/** Something a sync noticed but didn't fix. A dismissed finding stays dismissed on later runs. */
|
|
119
153
|
export interface AuditFindings {
|
|
120
154
|
id: string;
|
|
@@ -133,28 +167,22 @@ export interface AuditFindings {
|
|
|
133
167
|
export interface BoatClasses {
|
|
134
168
|
id: string;
|
|
135
169
|
name: string;
|
|
136
|
-
|
|
170
|
+
/** The most sailors one boat can safely take, such as 4 in an FJ. 0 for a coach boat. */
|
|
171
|
+
max_capacity: number;
|
|
137
172
|
/** Dry-storage area one boat takes. */
|
|
138
173
|
footprint_sq_ft: string | null;
|
|
174
|
+
/** 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. */
|
|
175
|
+
account_id: string | Accounts | null;
|
|
139
176
|
}
|
|
140
|
-
/**
|
|
177
|
+
/** A boat's own details, sail or power: its class and sail number. Sailors' own boats aren't listed here. */
|
|
141
178
|
export interface Boats {
|
|
142
179
|
id: string;
|
|
180
|
+
/** The asset this boat is. Its kind must be Sailboat or Powerboat. */
|
|
181
|
+
asset_id: string | Assets;
|
|
182
|
+
/** Such as Opti, 420, or RIB. */
|
|
143
183
|
boat_class_id: string | BoatClasses;
|
|
144
|
-
/**
|
|
145
|
-
name: string | null;
|
|
184
|
+
/** A sailboat's, if it has one. */
|
|
146
185
|
sail_number: string | null;
|
|
147
|
-
/** The hull identification number on the transom, if it's known. */
|
|
148
|
-
hull_number: string | null;
|
|
149
|
-
/** What the boat is worth. In cents: 12500 is $125.00. */
|
|
150
|
-
value_cents: number | null;
|
|
151
|
-
/** The day CSC got the boat. */
|
|
152
|
-
acquired_on: string | null;
|
|
153
|
-
/** The day the boat first sailed in a program. */
|
|
154
|
-
in_service_on: string | null;
|
|
155
|
-
/** The day CSC stopped using the boat. Empty while it's still in use. */
|
|
156
|
-
retired_on: string | null;
|
|
157
|
-
note: string | null;
|
|
158
186
|
}
|
|
159
187
|
/** 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. */
|
|
160
188
|
export interface CatalogOfferings {
|
|
@@ -428,6 +456,24 @@ export interface Households {
|
|
|
428
456
|
share_contact: boolean | null;
|
|
429
457
|
date_created: string | null;
|
|
430
458
|
}
|
|
459
|
+
/** The Links page in Sail Team. Drag the rows to change their order. */
|
|
460
|
+
export interface Links {
|
|
461
|
+
id: string;
|
|
462
|
+
title: string;
|
|
463
|
+
/** The page's address, starting with https://. */
|
|
464
|
+
url: string;
|
|
465
|
+
/** A line under the title, if it needs one. */
|
|
466
|
+
note: string | null;
|
|
467
|
+
sort: number | null;
|
|
468
|
+
/** The page's own title, read from the page when the link is saved. */
|
|
469
|
+
preview_title: string | null;
|
|
470
|
+
/** The page's own summary, read from the page when the link is saved. */
|
|
471
|
+
preview_description: string | null;
|
|
472
|
+
/** The address of the page's own picture, read from the page when the link is saved. */
|
|
473
|
+
preview_image: string | null;
|
|
474
|
+
/** When the page was last read. Empty if it couldn't be read: save the link again to retry. */
|
|
475
|
+
previewed_at: string | null;
|
|
476
|
+
}
|
|
431
477
|
/** A person's medical information, kept apart from People so only some staff see it. */
|
|
432
478
|
export interface MedicalProfiles {
|
|
433
479
|
id: string;
|
|
@@ -558,20 +604,8 @@ export interface PlanBlockBoats {
|
|
|
558
604
|
count: number;
|
|
559
605
|
/** How many boats sailors bring. They cost nothing and don't count against the organization's. */
|
|
560
606
|
sailor_owned: number | null;
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
export interface PlanBlockResources {
|
|
564
|
-
id: string;
|
|
565
|
-
/** The version this row belongs to. */
|
|
566
|
-
version_id: string | PlanVersions;
|
|
567
|
-
/** Stays the same across versions. Copy it where another row asks for this one's Key. */
|
|
568
|
-
key: string;
|
|
569
|
-
block_id: string | PlanActivityBlocks;
|
|
570
|
-
/** The resource it uses: copy the Key from a resource in the Operations Plan. */
|
|
571
|
-
resource_key: string;
|
|
572
|
-
quantity: string;
|
|
573
|
-
/** Hours each is used. Leave it empty for the block's time on the water (RIBs) or its paid time (other equipment). */
|
|
574
|
-
hours: string | null;
|
|
607
|
+
/** 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. */
|
|
608
|
+
chartered: number | null;
|
|
575
609
|
}
|
|
576
610
|
/** Who coaches a block: a number of staff at one staff level, or a salaried position. */
|
|
577
611
|
export interface PlanBlockStaff {
|
|
@@ -598,10 +632,12 @@ export interface PlanBoatRates {
|
|
|
598
632
|
/** Stays the same across versions. Copy it where another row asks for this one's Key. */
|
|
599
633
|
key: string;
|
|
600
634
|
boat_class_id: string | BoatClasses;
|
|
601
|
-
/** In cents: 12500 is $125.00. */
|
|
635
|
+
/** Upkeep for each hour one of CSC's boats of the class is on the water. In cents: 12500 is $125.00. */
|
|
602
636
|
maintenance_per_boat_hour_cents: number;
|
|
603
|
-
/**
|
|
604
|
-
|
|
637
|
+
/** Fuel for each hour one of CSC's boats of the class is on the water. In cents: 12500 is $125.00. */
|
|
638
|
+
fuel_per_hour_cents: number | null;
|
|
639
|
+
/** What one boat of the class costs to charter for a day. In cents: 12500 is $125.00. */
|
|
640
|
+
charter_per_day_cents: number | null;
|
|
605
641
|
}
|
|
606
642
|
/** The products a bonus owner owns. */
|
|
607
643
|
export interface PlanBonusOwnerProducts {
|
|
@@ -760,29 +796,29 @@ export interface PlanRates {
|
|
|
760
796
|
lodging_per_night_cents: number | null;
|
|
761
797
|
/** In cents: 12500 is $125.00. */
|
|
762
798
|
mileage_rate_cents: number | null;
|
|
763
|
-
/** The kind of resource whose cost prices coach boat (RIB) hours. */
|
|
764
|
-
rib_resource_kind_id: string | ResourceKinds | null;
|
|
765
799
|
}
|
|
766
|
-
/**
|
|
767
|
-
export interface
|
|
800
|
+
/** Each bonus in a Financial Plan, for each term, and for the offerings in no term on the year. 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. */
|
|
801
|
+
export interface PlanResultBonuses {
|
|
768
802
|
id: string;
|
|
769
|
-
/** The
|
|
770
|
-
version_id: string | PlanVersions;
|
|
771
|
-
/** Stays the same across versions. Copy it where another row asks for this one's Key. */
|
|
772
|
-
key: string;
|
|
773
|
-
resource_kind_id: string | ResourceKinds;
|
|
803
|
+
/** The bonus's role, such as Director. */
|
|
774
804
|
name: string;
|
|
775
|
-
|
|
776
|
-
/**
|
|
777
|
-
|
|
778
|
-
/**
|
|
779
|
-
|
|
780
|
-
/**
|
|
781
|
-
|
|
782
|
-
/** In cents: 12500 is $125.00. */
|
|
783
|
-
|
|
784
|
-
/**
|
|
785
|
-
|
|
805
|
+
term_key: string | null;
|
|
806
|
+
/** The term it's paid on. Empty for the offerings in no term, such as Adult Learn to Sail, whose bonus is worked out on the year. */
|
|
807
|
+
term_name: string | null;
|
|
808
|
+
/** The term's first day, which puts the terms in order. */
|
|
809
|
+
term_starts_on: string | null;
|
|
810
|
+
/** A share of the net. 0.05 means 5%. */
|
|
811
|
+
bonus_rate: string;
|
|
812
|
+
/** The net of its products in the term, which the bonus is figured on. In cents: 12500 is $125.00. */
|
|
813
|
+
net_cents: number;
|
|
814
|
+
/** Its rate × the net, or nothing when the net is a loss. In cents: 12500 is $125.00. */
|
|
815
|
+
bonus_cents: number;
|
|
816
|
+
bonus_owner_key: string;
|
|
817
|
+
program_area_key: string | null;
|
|
818
|
+
/** The Financial Plan these results are for. */
|
|
819
|
+
version_set_id: string | VersionSets;
|
|
820
|
+
/** The plan in the Financial Plan that pays the bonus. */
|
|
821
|
+
version_set_member_id: string | VersionSetMembers | null;
|
|
786
822
|
}
|
|
787
823
|
/** 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. */
|
|
788
824
|
export interface PlanResultCapital {
|
|
@@ -823,7 +859,7 @@ export interface PlanResultLines {
|
|
|
823
859
|
/** A Financial Plan's P&L for each offering, product, and program area. 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. */
|
|
824
860
|
export interface PlanResultPnl {
|
|
825
861
|
id: string;
|
|
826
|
-
level: "offering" | "product" | "program_area";
|
|
862
|
+
level: "offering" | "product" | "program_area" | "program_area_term";
|
|
827
863
|
subject_key: string;
|
|
828
864
|
name: string;
|
|
829
865
|
/** In cents: 12500 is $125.00. */
|
|
@@ -841,6 +877,11 @@ export interface PlanResultPnl {
|
|
|
841
877
|
version_set_id: string | VersionSets;
|
|
842
878
|
/** The plan in the Financial Plan that holds what it's charged to. */
|
|
843
879
|
version_set_member_id: string | VersionSetMembers | null;
|
|
880
|
+
term_key: string | null;
|
|
881
|
+
/** For a program area by term, the term. Empty for its offerings in no term, such as Adult Learn to Sail. */
|
|
882
|
+
term_name: string | null;
|
|
883
|
+
/** The term's first day, which puts the terms in order. */
|
|
884
|
+
term_starts_on: string | null;
|
|
844
885
|
}
|
|
845
886
|
/** Each salaried position's pay for the year, in a Financial Plan. 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. */
|
|
846
887
|
export interface PlanResultPositions {
|
|
@@ -1121,11 +1162,6 @@ export interface Registrations {
|
|
|
1121
1162
|
status: "confirmed" | "waitlist" | "cancelled";
|
|
1122
1163
|
clubspot_entry_id: string | ClubspotRegistrationEntries;
|
|
1123
1164
|
}
|
|
1124
|
-
/** A kind of shared resource, such as RIB or trailer. */
|
|
1125
|
-
export interface ResourceKinds {
|
|
1126
|
-
id: string;
|
|
1127
|
-
name: string;
|
|
1128
|
-
}
|
|
1129
1165
|
/** The schools a sailor can sail for, which families pick from. A school's other names match what families typed on their Clubspot forms. */
|
|
1130
1166
|
export interface Schools {
|
|
1131
1167
|
id: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyc-seattle/platform",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.51.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",
|