@updraft-solutions/mcrit-sdk 0.3.1 → 0.5.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/dist/chunk-AKLFMP7G.cjs +2525 -0
- package/dist/{chunk-MF7G76QS.cjs → chunk-IVMOR5SW.cjs} +26 -1
- package/dist/{chunk-D5DUVW34.js → chunk-KIMCBEY7.js} +26 -1
- package/dist/chunk-YCIXOVEC.js +2525 -0
- package/dist/format/index.cjs +2 -2
- package/dist/format/index.d.cts +13 -4
- package/dist/format/index.d.ts +13 -4
- package/dist/format/index.js +1 -1
- package/dist/index.cjs +201 -5
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +208 -12
- package/dist/schemas/index.cjs +198 -2
- package/dist/schemas/index.d.cts +3157 -128
- package/dist/schemas/index.d.ts +3157 -128
- package/dist/schemas/index.js +201 -5
- package/dist/{models-BWmpcfR8.d.cts → training-Der1DPU-.d.cts} +23757 -6528
- package/dist/{models-BWmpcfR8.d.ts → training-Der1DPU-.d.ts} +23757 -6528
- package/dist/types/index.cjs +1 -1
- package/dist/types/index.d.cts +277 -6
- package/dist/types/index.d.ts +277 -6
- package/dist/types/index.js +1 -1
- package/package.json +1 -1
- package/src/format/index.ts +51 -5
- package/src/index.ts +39 -0
- package/src/schemas/common.ts +37 -0
- package/src/schemas/course-milestone.ts +78 -0
- package/src/schemas/equipment.ts +27 -0
- package/src/schemas/form-template.ts +413 -0
- package/src/schemas/index.ts +7 -1
- package/src/schemas/location-map.ts +175 -0
- package/src/schemas/models.ts +110 -28
- package/src/schemas/newsletter.ts +362 -0
- package/src/schemas/todo.ts +46 -0
- package/src/schemas/training.ts +500 -0
- package/src/types/ai.ts +85 -0
- package/src/types/aircraft-block.ts +23 -0
- package/src/types/api.ts +3 -1
- package/src/types/background-task.ts +28 -0
- package/src/types/compliance.ts +27 -3
- package/src/types/index.ts +5 -0
- package/src/types/landing-fee.ts +92 -0
- package/src/types/models.ts +44 -0
- package/src/types/roles.ts +26 -0
- package/dist/chunk-7QF3OJ45.cjs +0 -1325
- package/dist/chunk-FS5I4MBG.js +0 -1325
- package/src/schemas/fee.ts +0 -37
- /package/dist/{chunk-LXNCAKJZ.js → chunk-2WJHTRIE.js} +0 -0
- /package/dist/{chunk-MOOGM3DW.cjs → chunk-DCEOET63.cjs} +0 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// ── Grading scales ──
|
|
4
|
+
|
|
5
|
+
// No i18n in scale data — a plain German `key` + `description`, verbatim
|
|
6
|
+
// from the source paper form (e.g. J3). The UI shows the key (chip/button),
|
|
7
|
+
// the description as a tooltip. App-level i18n (our own UI labels) is
|
|
8
|
+
// unaffected — only DATA i18n (per-point `{de,en,no}` labels) was removed.
|
|
9
|
+
export const gradingScalePointSchema = z.object({
|
|
10
|
+
key: z.string(),
|
|
11
|
+
description: z.string(),
|
|
12
|
+
});
|
|
13
|
+
export type GradingScalePoint = z.infer<typeof gradingScalePointSchema>;
|
|
14
|
+
|
|
15
|
+
export const gradingScaleSchema = z.object({
|
|
16
|
+
id: z.number(),
|
|
17
|
+
name: z.string(),
|
|
18
|
+
points: z.array(gradingScalePointSchema),
|
|
19
|
+
});
|
|
20
|
+
export type GradingScale = z.infer<typeof gradingScaleSchema>;
|
|
21
|
+
|
|
22
|
+
export const GradingScaleFormSchema = z.object({
|
|
23
|
+
name: z.string().min(1).max(255),
|
|
24
|
+
points: z.array(gradingScalePointSchema).min(2),
|
|
25
|
+
});
|
|
26
|
+
export type GradingScaleForm = z.infer<typeof GradingScaleFormSchema>;
|
|
27
|
+
|
|
28
|
+
// ── Training elements (catalog) ──
|
|
29
|
+
|
|
30
|
+
export const TrainingElementKindEnum = z.enum([
|
|
31
|
+
"flight_exercise",
|
|
32
|
+
"theory",
|
|
33
|
+
"briefing",
|
|
34
|
+
"check",
|
|
35
|
+
]);
|
|
36
|
+
export type TrainingElementKind = z.infer<typeof TrainingElementKindEnum>;
|
|
37
|
+
|
|
38
|
+
export const trainingElementSchema = z.object({
|
|
39
|
+
id: z.number(),
|
|
40
|
+
code: z.string(),
|
|
41
|
+
name: z.string(),
|
|
42
|
+
description: z.string().nullable().optional(),
|
|
43
|
+
kind: TrainingElementKindEnum,
|
|
44
|
+
grading_scale_id: z.number().nullable().optional(),
|
|
45
|
+
grading_scale: gradingScaleSchema.nullable(),
|
|
46
|
+
is_active: z.boolean(),
|
|
47
|
+
// Where-used count — how many of the company's syllabi carry this
|
|
48
|
+
// element (`TrainingElementController::index`'s `attached_syllabi_count`
|
|
49
|
+
// withCount alias). Absent on any payload that doesn't count it.
|
|
50
|
+
attached_syllabi_count: z.number().optional(),
|
|
51
|
+
// Provenance stamp (which template element this row was seeded/adopted
|
|
52
|
+
// from) — not yet exposed by TrainingElementResource; optional/forward-
|
|
53
|
+
// compatible.
|
|
54
|
+
template_element_id: z.number().nullable().optional(),
|
|
55
|
+
});
|
|
56
|
+
export type TrainingElement = z.infer<typeof trainingElementSchema>;
|
|
57
|
+
|
|
58
|
+
export const TrainingElementFormSchema = z.object({
|
|
59
|
+
code: z.string().min(1).max(32),
|
|
60
|
+
name: z.string().min(1).max(255),
|
|
61
|
+
description: z.string().nullable().optional(),
|
|
62
|
+
kind: TrainingElementKindEnum,
|
|
63
|
+
grading_scale_id: z.number().nullable().optional(),
|
|
64
|
+
is_active: z.boolean().optional(),
|
|
65
|
+
});
|
|
66
|
+
export type TrainingElementForm = z.infer<typeof TrainingElementFormSchema>;
|
|
67
|
+
|
|
68
|
+
// ── Syllabus (first-class, company-owned — see
|
|
69
|
+
// docs/superpowers/specs/2026-08-03-syllabus-model-design.md) ──
|
|
70
|
+
|
|
71
|
+
// A syllabus's kind (Amendment 3 — progress checks): `regular` drives the
|
|
72
|
+
// Übersicht / per-flight Ausbildungsnachweis as before; `progress_check`
|
|
73
|
+
// marks a check sheet (A41–A44 / D4 style) that is attached to courses via
|
|
74
|
+
// `course_check_syllabi` and filled on demand per booking. `.default` keeps
|
|
75
|
+
// pre-amendment fixtures/payloads parseable while the inferred output type
|
|
76
|
+
// stays required.
|
|
77
|
+
export const SyllabusKindEnum = z.enum(["regular", "progress_check"]);
|
|
78
|
+
export type SyllabusKind = z.infer<typeof SyllabusKindEnum>;
|
|
79
|
+
|
|
80
|
+
export const syllabusElementSchema = z.object({
|
|
81
|
+
id: z.number(),
|
|
82
|
+
syllabus_id: z.number(),
|
|
83
|
+
// Embedded so a row still renders once its element is deactivated;
|
|
84
|
+
// `.nullable().optional()` mirrors `trainingRecordEntrySchema`'s own
|
|
85
|
+
// embedded-element convention — the backend omits the key entirely on any
|
|
86
|
+
// response that didn't eager-load the relation.
|
|
87
|
+
training_element: trainingElementSchema.nullable().optional(),
|
|
88
|
+
// The sheet's section header (was `phase` pre-amendment). `null` = no
|
|
89
|
+
// group declared.
|
|
90
|
+
group: z.string().nullable().optional(),
|
|
91
|
+
sort_order: z.number(),
|
|
92
|
+
is_required_each_flight: z.boolean(),
|
|
93
|
+
});
|
|
94
|
+
export type SyllabusElement = z.infer<typeof syllabusElementSchema>;
|
|
95
|
+
|
|
96
|
+
export const AttachSyllabusElementFormSchema = z.object({
|
|
97
|
+
training_element_id: z.number(),
|
|
98
|
+
group: z.string().nullable().optional(),
|
|
99
|
+
sort_order: z.number().int().min(0).optional(),
|
|
100
|
+
is_required_each_flight: z.boolean().optional(),
|
|
101
|
+
});
|
|
102
|
+
export type AttachSyllabusElementForm = z.infer<
|
|
103
|
+
typeof AttachSyllabusElementFormSchema
|
|
104
|
+
>;
|
|
105
|
+
|
|
106
|
+
export const UpdateSyllabusElementFormSchema = z.object({
|
|
107
|
+
group: z.string().nullable().optional(),
|
|
108
|
+
sort_order: z.number().int().min(0).optional(),
|
|
109
|
+
is_required_each_flight: z.boolean().optional(),
|
|
110
|
+
});
|
|
111
|
+
export type UpdateSyllabusElementForm = z.infer<
|
|
112
|
+
typeof UpdateSyllabusElementFormSchema
|
|
113
|
+
>;
|
|
114
|
+
|
|
115
|
+
// One row of the `POST syllabi/{syllabus}/elements/reorder` payload — the
|
|
116
|
+
// full flat order AND the (frontend-computed, authoritative) `group` per
|
|
117
|
+
// row in one pass. `group` is the corrected value after group-adoption-on-
|
|
118
|
+
// drag (see `app/utils/syllabusGroupAdoption.ts`), not just the row's prior
|
|
119
|
+
// value — a drag that moves a row into another group's block changes its
|
|
120
|
+
// group, not only its position.
|
|
121
|
+
export const ReorderSyllabusElementItemSchema = z.object({
|
|
122
|
+
id: z.number(),
|
|
123
|
+
group: z.string().nullable(),
|
|
124
|
+
});
|
|
125
|
+
export type ReorderSyllabusElementItem = z.infer<
|
|
126
|
+
typeof ReorderSyllabusElementItemSchema
|
|
127
|
+
>;
|
|
128
|
+
|
|
129
|
+
// One lean entry per distinct course referencing a syllabus via either
|
|
130
|
+
// pointer or a `course_check_syllabi` attachment
|
|
131
|
+
// (`SyllabusResource::buildUsedByCourses` — deduped, a course using both
|
|
132
|
+
// pointers gets a single `as: "both"` entry, never two rows; `"check"` marks
|
|
133
|
+
// a check-sheet attachment, which kind enforcement keeps disjoint from the
|
|
134
|
+
// pointer tags — `"both"` stays pointer-only).
|
|
135
|
+
export const usedByCourseSchema = z.object({
|
|
136
|
+
id: z.number(),
|
|
137
|
+
name: z.string(),
|
|
138
|
+
as: z.enum(["syllabus", "booking_form", "both", "check"]),
|
|
139
|
+
});
|
|
140
|
+
export type UsedByCourse = z.infer<typeof usedByCourseSchema>;
|
|
141
|
+
|
|
142
|
+
export const syllabusSchema = z.object({
|
|
143
|
+
id: z.number(),
|
|
144
|
+
company_id: z.number(),
|
|
145
|
+
name: z.string(),
|
|
146
|
+
description: z.string().nullable().optional(),
|
|
147
|
+
// `regular` | `progress_check` — a CONTENT field like
|
|
148
|
+
// `has_flight_evaluation`: changing it on a published, in-use revision
|
|
149
|
+
// auto-spawns a draft.
|
|
150
|
+
kind: SyllabusKindEnum.default("regular"),
|
|
151
|
+
// Gates whether the per-flight record renders/validates section 2
|
|
152
|
+
// (Gesamtbewertung + the three "Anmerkungen des Fluglehrers" prompts).
|
|
153
|
+
has_flight_evaluation: z.boolean(),
|
|
154
|
+
// Gates whether `ready-to-sign` issues a student countersignature
|
|
155
|
+
// alongside the instructor's — per-syllabus (Amendment 2, replaces the
|
|
156
|
+
// former company-wide toggle).
|
|
157
|
+
requires_student_signature: z.boolean(),
|
|
158
|
+
// ── Versioning (Amendment 2) — always present, flat (no server-side
|
|
159
|
+
// lineage grouping; the frontend groups client-side from these fields). ──
|
|
160
|
+
version: z.number(),
|
|
161
|
+
root_syllabus_id: z.number().nullable(),
|
|
162
|
+
is_draft: z.boolean(),
|
|
163
|
+
// Derived server-side from the same eager-loaded where-used collections as
|
|
164
|
+
// the two `courses_as_*_count` fields below — same availability (`index`
|
|
165
|
+
// AND `show`, absent on `store`/`update`).
|
|
166
|
+
is_in_use: z.boolean().optional(),
|
|
167
|
+
// Transient, request-scoped: present (non-null) only on the response of a
|
|
168
|
+
// mutation that just auto-spawned THIS row as the new draft — never a
|
|
169
|
+
// persisted property. The frontend's signal to toast-explain and
|
|
170
|
+
// `router.replace` onto this syllabus's own page.
|
|
171
|
+
spawned_from_id: z.number().nullable().optional(),
|
|
172
|
+
// Only present when the relation was loaded (`show`/`store`/`update` —
|
|
173
|
+
// never on the `index` list).
|
|
174
|
+
elements: z.array(syllabusElementSchema).optional(),
|
|
175
|
+
// `withCount('elements')` — present on `index` AND `show`; absent on
|
|
176
|
+
// `store`/`update` responses (use `elements.length` there instead, since
|
|
177
|
+
// those always eager-load the full `elements` relation anyway).
|
|
178
|
+
elements_count: z.number().optional(),
|
|
179
|
+
// Where-used — present on `index` AND `show` (both eager-load both
|
|
180
|
+
// `coursesAs*` relations); absent on `store`/`update`.
|
|
181
|
+
courses_as_syllabus_count: z.number().optional(),
|
|
182
|
+
courses_as_booking_form_syllabus_count: z.number().optional(),
|
|
183
|
+
// One deduped entry per distinct course referencing this syllabus via
|
|
184
|
+
// either pointer (`as: "both"` when a course uses it for both). Same
|
|
185
|
+
// availability as the two counts above.
|
|
186
|
+
used_by_courses: z.array(usedByCourseSchema).optional(),
|
|
187
|
+
// Heavier metric, `show` only.
|
|
188
|
+
materialized_enrollment_count: z.number().optional(),
|
|
189
|
+
// Server-evaluated per-instance policy decisions (`HasGatesOnRequest`) —
|
|
190
|
+
// present when the fetch sends `permissions[syllabus]=*`. Loose record on
|
|
191
|
+
// purpose, same rationale as `companySchema.can`.
|
|
192
|
+
can: z.record(z.string(), z.boolean().nullable()).optional(),
|
|
193
|
+
created_at: z.string().optional(),
|
|
194
|
+
updated_at: z.string().optional(),
|
|
195
|
+
});
|
|
196
|
+
export type Syllabus = z.infer<typeof syllabusSchema>;
|
|
197
|
+
|
|
198
|
+
export const StoreSyllabusFormSchema = z.object({
|
|
199
|
+
name: z.string().min(1).max(255),
|
|
200
|
+
description: z.string().nullable().optional(),
|
|
201
|
+
kind: SyllabusKindEnum.optional(),
|
|
202
|
+
has_flight_evaluation: z.boolean().optional(),
|
|
203
|
+
requires_student_signature: z.boolean().optional(),
|
|
204
|
+
});
|
|
205
|
+
export type StoreSyllabusForm = z.infer<typeof StoreSyllabusFormSchema>;
|
|
206
|
+
|
|
207
|
+
export const UpdateSyllabusFormSchema = z.object({
|
|
208
|
+
name: z.string().min(1).max(255).optional(),
|
|
209
|
+
description: z.string().nullable().optional(),
|
|
210
|
+
kind: SyllabusKindEnum.optional(),
|
|
211
|
+
has_flight_evaluation: z.boolean().optional(),
|
|
212
|
+
requires_student_signature: z.boolean().optional(),
|
|
213
|
+
});
|
|
214
|
+
export type UpdateSyllabusForm = z.infer<typeof UpdateSyllabusFormSchema>;
|
|
215
|
+
|
|
216
|
+
// The lean `{id, name, version, kind}` shape `CourseResource` /
|
|
217
|
+
// `BaseCourseResource` serialize for each attached check syllabus
|
|
218
|
+
// (`check_syllabi`, present on course show/update responses).
|
|
219
|
+
export const courseCheckSyllabusSchema = z.object({
|
|
220
|
+
id: z.number(),
|
|
221
|
+
name: z.string(),
|
|
222
|
+
version: z.number(),
|
|
223
|
+
kind: SyllabusKindEnum.default("regular"),
|
|
224
|
+
});
|
|
225
|
+
export type CourseCheckSyllabus = z.infer<typeof courseCheckSyllabusSchema>;
|
|
226
|
+
|
|
227
|
+
// A syllabus-element mutation endpoint's uniform response (attach/update/
|
|
228
|
+
// detach/reorder all return this shape — never a bare 204 — precisely so a
|
|
229
|
+
// spawn can be detected and the frontend can redirect in one round trip).
|
|
230
|
+
export const syllabusElementMutationResultSchema = z.object({
|
|
231
|
+
syllabus: syllabusSchema,
|
|
232
|
+
element: syllabusElementSchema.nullable(),
|
|
233
|
+
spawned_from_id: z.number().nullable(),
|
|
234
|
+
});
|
|
235
|
+
export type SyllabusElementMutationResult = z.infer<
|
|
236
|
+
typeof syllabusElementMutationResultSchema
|
|
237
|
+
>;
|
|
238
|
+
|
|
239
|
+
// `POST syllabi/{syllabus}/rollout` response. `pointer: "check"` rows are
|
|
240
|
+
// `course_check_syllabi` re-attachments (Amendment 3).
|
|
241
|
+
export const syllabusRolloutResultItemSchema = z.object({
|
|
242
|
+
course_id: z.number(),
|
|
243
|
+
course_name: z.string(),
|
|
244
|
+
pointer: z.enum(["syllabus", "booking_form", "check"]),
|
|
245
|
+
});
|
|
246
|
+
// Attachments left on the older revision because the rolled-out revision's
|
|
247
|
+
// kind doesn't fit that attachment type (`RolloutSyllabusRevision`'s kind
|
|
248
|
+
// guard: pointers need regular, the check pivot needs progress_check) — the
|
|
249
|
+
// rollout surface must report these, never silently drop them.
|
|
250
|
+
export const syllabusRolloutSkippedItemSchema =
|
|
251
|
+
syllabusRolloutResultItemSchema.extend({
|
|
252
|
+
reason: z.enum(["kind_mismatch"]),
|
|
253
|
+
});
|
|
254
|
+
export const syllabusRolloutResultSchema = z.object({
|
|
255
|
+
results: z.array(syllabusRolloutResultItemSchema),
|
|
256
|
+
skipped: z.array(syllabusRolloutSkippedItemSchema),
|
|
257
|
+
});
|
|
258
|
+
export type SyllabusRolloutResultItem = z.infer<
|
|
259
|
+
typeof syllabusRolloutResultItemSchema
|
|
260
|
+
>;
|
|
261
|
+
export type SyllabusRolloutSkippedItem = z.infer<
|
|
262
|
+
typeof syllabusRolloutSkippedItemSchema
|
|
263
|
+
>;
|
|
264
|
+
export type SyllabusRolloutResult = z.infer<typeof syllabusRolloutResultSchema>;
|
|
265
|
+
|
|
266
|
+
// ── Enrollment training Übersicht ──
|
|
267
|
+
|
|
268
|
+
export const EnrollmentTrainingStatusEnum = z.enum([
|
|
269
|
+
"not_started",
|
|
270
|
+
"in_training",
|
|
271
|
+
"completed",
|
|
272
|
+
"removed_from_syllabus",
|
|
273
|
+
]);
|
|
274
|
+
export type EnrollmentTrainingStatus = z.infer<
|
|
275
|
+
typeof EnrollmentTrainingStatusEnum
|
|
276
|
+
>;
|
|
277
|
+
|
|
278
|
+
export const gradeHistoryItemSchema = z.object({
|
|
279
|
+
training_record_id: z.number(),
|
|
280
|
+
booking_id: z.number(),
|
|
281
|
+
date: z.string().nullable().optional(),
|
|
282
|
+
grade_key: z.string().nullable().optional(),
|
|
283
|
+
comment: z.string().nullable().optional(),
|
|
284
|
+
instructor_name: z.string().nullable().optional(),
|
|
285
|
+
});
|
|
286
|
+
export type GradeHistoryItem = z.infer<typeof gradeHistoryItemSchema>;
|
|
287
|
+
|
|
288
|
+
export const enrollmentTrainingElementSchema = z.object({
|
|
289
|
+
id: z.number(),
|
|
290
|
+
course_enrollment_id: z.number(),
|
|
291
|
+
status: EnrollmentTrainingStatusEnum,
|
|
292
|
+
// Denormalized from the course's (Übersicht-driving) syllabus row at
|
|
293
|
+
// materialization time — was `phase` pre-amendment.
|
|
294
|
+
group_label: z.string().nullable().optional(),
|
|
295
|
+
sort_order: z.number(),
|
|
296
|
+
signed_off_at: z.string().nullable(),
|
|
297
|
+
signed_off_by: z
|
|
298
|
+
.object({
|
|
299
|
+
id: z.number(),
|
|
300
|
+
name: z.string().optional(),
|
|
301
|
+
})
|
|
302
|
+
.nullable(),
|
|
303
|
+
notes: z.string().nullable().optional(),
|
|
304
|
+
training_element: trainingElementSchema,
|
|
305
|
+
grade_history: z.array(gradeHistoryItemSchema),
|
|
306
|
+
latest_grade_key: z.string().nullable().optional(),
|
|
307
|
+
});
|
|
308
|
+
export type EnrollmentTrainingElement = z.infer<
|
|
309
|
+
typeof enrollmentTrainingElementSchema
|
|
310
|
+
>;
|
|
311
|
+
|
|
312
|
+
export const SignOffTrainingElementFormSchema = z.object({
|
|
313
|
+
notes: z.string().nullable().optional(),
|
|
314
|
+
signed_off_by_membership_id: z.number().nullable().optional(),
|
|
315
|
+
});
|
|
316
|
+
export type SignOffTrainingElementForm = z.infer<
|
|
317
|
+
typeof SignOffTrainingElementFormSchema
|
|
318
|
+
>;
|
|
319
|
+
|
|
320
|
+
// ── Training records (per-flight) ──
|
|
321
|
+
|
|
322
|
+
export const TrainingRecordStatusEnum = z.enum([
|
|
323
|
+
"draft",
|
|
324
|
+
"awaiting_signatures",
|
|
325
|
+
"signed",
|
|
326
|
+
]);
|
|
327
|
+
export type TrainingRecordStatus = z.infer<typeof TrainingRecordStatusEnum>;
|
|
328
|
+
|
|
329
|
+
// The pass/fail "Ergebnis" of a progress check — only records stamped with a
|
|
330
|
+
// check-kind sheet may carry it (backend 422s otherwise), and it is required
|
|
331
|
+
// before ready-to-sign on those records.
|
|
332
|
+
export const TrainingRecordResultEnum = z.enum(["passed", "failed"]);
|
|
333
|
+
export type TrainingRecordResult = z.infer<typeof TrainingRecordResultEnum>;
|
|
334
|
+
|
|
335
|
+
export const trainingRecordEntrySchema = z.object({
|
|
336
|
+
id: z.number(),
|
|
337
|
+
training_element_id: z.number(),
|
|
338
|
+
// Embedded so a signed record (immutable evidence) still renders fully
|
|
339
|
+
// even if the element is later detached from the syllabus —
|
|
340
|
+
// callers must prefer this over resolving `training_element_id` against
|
|
341
|
+
// the (mutable, current) syllabus. `.nullable().optional()`: the backend
|
|
342
|
+
// omits the key entirely (via `whenLoaded`) on any response that didn't
|
|
343
|
+
// eager-load the relation, and could resolve it to `null` in a genuinely
|
|
344
|
+
// orphaned edge case — both degrade to "treat as unresolved", not a
|
|
345
|
+
// parse failure.
|
|
346
|
+
training_element: trainingElementSchema.nullable().optional(),
|
|
347
|
+
grade_key: z.string().nullable().optional(),
|
|
348
|
+
comment: z.string().nullable().optional(),
|
|
349
|
+
});
|
|
350
|
+
export type TrainingRecordEntry = z.infer<typeof trainingRecordEntrySchema>;
|
|
351
|
+
|
|
352
|
+
export const trainingRecordEntryInputSchema = z.object({
|
|
353
|
+
training_element_id: z.number(),
|
|
354
|
+
grade_key: z.string().nullable().optional(),
|
|
355
|
+
comment: z.string().nullable().optional(),
|
|
356
|
+
});
|
|
357
|
+
export type TrainingRecordEntryInput = z.infer<
|
|
358
|
+
typeof trainingRecordEntryInputSchema
|
|
359
|
+
>;
|
|
360
|
+
|
|
361
|
+
// Mirrors the generic `SignatureResource` (App\Http\Resources\Form) — the
|
|
362
|
+
// same polymorphic `Signature` model backs both form submissions and
|
|
363
|
+
// training records. Kept self-contained here (not imported from
|
|
364
|
+
// @schemas/FormTemplateSchema) to match this file's own alias root and the
|
|
365
|
+
// courseMilestone.ts convention of no cross-schema imports.
|
|
366
|
+
export const trainingSignatureSchema = z.object({
|
|
367
|
+
type: z.literal("signature").optional(),
|
|
368
|
+
id: z.number(),
|
|
369
|
+
signable_type: z.string().optional(),
|
|
370
|
+
signable_id: z.number().optional(),
|
|
371
|
+
field_key: z.string(),
|
|
372
|
+
status: z.enum(["pending", "signed", "revoked"]),
|
|
373
|
+
required_signer_membership_id: z.number().nullable(),
|
|
374
|
+
required_signer_name: z.string().nullable().optional(),
|
|
375
|
+
required_signer_permission: z.string().nullable().optional(),
|
|
376
|
+
signed_by_user_id: z.number().nullable(),
|
|
377
|
+
signed_by_membership_id: z.number().nullable(),
|
|
378
|
+
signed_by_name: z.string().nullable().optional(),
|
|
379
|
+
is_current_user_designated: z.boolean().optional().default(false),
|
|
380
|
+
signed_at: z.string().nullable(),
|
|
381
|
+
image: z.string().nullable(),
|
|
382
|
+
data_hash: z.string().nullable(),
|
|
383
|
+
schema_version: z.number().nullable(),
|
|
384
|
+
schema_hash: z.string().nullable(),
|
|
385
|
+
consent_text_hash: z.string().nullable(),
|
|
386
|
+
auth_method: z.string().nullable(),
|
|
387
|
+
auth_verified_at: z.string().nullable(),
|
|
388
|
+
ip_address: z.string().nullable(),
|
|
389
|
+
user_agent: z.string().nullable(),
|
|
390
|
+
geo_country: z.string().nullable(),
|
|
391
|
+
signing_event_id: z.string().nullable(),
|
|
392
|
+
created_at: z.string().optional(),
|
|
393
|
+
updated_at: z.string().optional(),
|
|
394
|
+
});
|
|
395
|
+
export type TrainingSignature = z.infer<typeof trainingSignatureSchema>;
|
|
396
|
+
|
|
397
|
+
export const trainingRecordSchema = z.object({
|
|
398
|
+
id: z.number(),
|
|
399
|
+
booking_id: z.number(),
|
|
400
|
+
course_enrollment_id: z.number(),
|
|
401
|
+
instructor_membership_id: z.number().nullable().optional(),
|
|
402
|
+
status: TrainingRecordStatusEnum,
|
|
403
|
+
// Whether section 2 (Gesamtbewertung + Anmerkungen) applies at all — read
|
|
404
|
+
// live off the course's booking-form syllabus by
|
|
405
|
+
// `TrainingRecordResource`, the single source of truth. `false` (never
|
|
406
|
+
// absent) when there's no booking-form syllabus at all.
|
|
407
|
+
has_flight_evaluation: z.boolean().default(false),
|
|
408
|
+
// The exact booking-form syllabus revision stamped at CREATE time (never
|
|
409
|
+
// on a later update) — the audit-chain provenance ("Rev. n this session
|
|
410
|
+
// was graded against"). `syllabus` is `null` when the course had no
|
|
411
|
+
// booking-form syllabus assigned at creation time.
|
|
412
|
+
syllabus_id: z.number().nullable().optional(),
|
|
413
|
+
// `kind` is the frontend's signal to render the check-only "Ergebnis"
|
|
414
|
+
// section for this record.
|
|
415
|
+
syllabus: z
|
|
416
|
+
.object({
|
|
417
|
+
id: z.number(),
|
|
418
|
+
name: z.string(),
|
|
419
|
+
version: z.number(),
|
|
420
|
+
kind: SyllabusKindEnum.default("regular"),
|
|
421
|
+
})
|
|
422
|
+
.nullable()
|
|
423
|
+
.optional(),
|
|
424
|
+
// Non-null only on check-kind records (see TrainingRecordResultEnum).
|
|
425
|
+
result: TrainingRecordResultEnum.nullable().optional(),
|
|
426
|
+
// The J3-mirroring fields — the Gesamtbewertung (a point key from the
|
|
427
|
+
// booking-form syllabus's element grading scales) plus the three
|
|
428
|
+
// "Anmerkungen des Fluglehrers" comment sections from the paper form.
|
|
429
|
+
overall_grade_key: z.string().nullable().optional(),
|
|
430
|
+
what_went_well: z.string().nullable().optional(),
|
|
431
|
+
needs_improvement: z.string().nullable().optional(),
|
|
432
|
+
repeat_next_flight: z.string().nullable().optional(),
|
|
433
|
+
entries: z.array(trainingRecordEntrySchema),
|
|
434
|
+
signatures: z.array(trainingSignatureSchema),
|
|
435
|
+
created_at: z.string().optional(),
|
|
436
|
+
updated_at: z.string().optional(),
|
|
437
|
+
});
|
|
438
|
+
export type TrainingRecord = z.infer<typeof trainingRecordSchema>;
|
|
439
|
+
|
|
440
|
+
export const UpsertTrainingRecordFormSchema = z.object({
|
|
441
|
+
// CREATE only — which sheet this record fills (the course's booking-form
|
|
442
|
+
// syllabus when omitted, or one of its attached check syllabi). The choice
|
|
443
|
+
// IS the stamp; PATCH prohibits this key entirely (the stamp never moves).
|
|
444
|
+
syllabus_id: z.number().nullable().optional(),
|
|
445
|
+
// Check-kind sheets only — 422 on any other sheet.
|
|
446
|
+
result: TrainingRecordResultEnum.nullable().optional(),
|
|
447
|
+
overall_grade_key: z.string().nullable().optional(),
|
|
448
|
+
what_went_well: z.string().nullable().optional(),
|
|
449
|
+
needs_improvement: z.string().nullable().optional(),
|
|
450
|
+
repeat_next_flight: z.string().nullable().optional(),
|
|
451
|
+
instructor_membership_id: z.number().nullable().optional(),
|
|
452
|
+
entries: z.array(trainingRecordEntryInputSchema),
|
|
453
|
+
});
|
|
454
|
+
export type UpsertTrainingRecordForm = z.infer<
|
|
455
|
+
typeof UpsertTrainingRecordFormSchema
|
|
456
|
+
>;
|
|
457
|
+
|
|
458
|
+
// ── Training template packs (syllabus templates) ──
|
|
459
|
+
|
|
460
|
+
export const trainingTemplatePackSchema = z.object({
|
|
461
|
+
key: z.string(),
|
|
462
|
+
name: z.string(),
|
|
463
|
+
description: z.string().nullable().optional(),
|
|
464
|
+
// Carried onto the syllabus the pack creates (regular vs. progress check).
|
|
465
|
+
kind: SyllabusKindEnum.default("regular"),
|
|
466
|
+
element_count: z.number().optional(),
|
|
467
|
+
});
|
|
468
|
+
export type TrainingTemplatePack = z.infer<typeof trainingTemplatePackSchema>;
|
|
469
|
+
|
|
470
|
+
export const CreateSyllabusFromTemplateFormSchema = z.object({
|
|
471
|
+
template_key: z.string().min(1),
|
|
472
|
+
});
|
|
473
|
+
export type CreateSyllabusFromTemplateForm = z.infer<
|
|
474
|
+
typeof CreateSyllabusFromTemplateFormSchema
|
|
475
|
+
>;
|
|
476
|
+
|
|
477
|
+
// Mirrors `CreateSyllabusFromTemplate::run()`'s return shape (m-crit-api),
|
|
478
|
+
// as wrapped by `SyllabusController::fromTemplate`.
|
|
479
|
+
export const createSyllabusFromTemplateResultSchema = z.object({
|
|
480
|
+
catalog: z.object({
|
|
481
|
+
created: z.number(),
|
|
482
|
+
existing: z.number(),
|
|
483
|
+
adopted: z.number(),
|
|
484
|
+
mismatched: z.array(
|
|
485
|
+
z.object({
|
|
486
|
+
code: z.string(),
|
|
487
|
+
company_name: z.string(),
|
|
488
|
+
template_name: z.string(),
|
|
489
|
+
}),
|
|
490
|
+
),
|
|
491
|
+
}),
|
|
492
|
+
elements: z.object({
|
|
493
|
+
created: z.number(),
|
|
494
|
+
existing: z.number(),
|
|
495
|
+
}),
|
|
496
|
+
syllabus: syllabusSchema,
|
|
497
|
+
});
|
|
498
|
+
export type CreateSyllabusFromTemplateResult = z.infer<
|
|
499
|
+
typeof createSyllabusFromTemplateResultSchema
|
|
500
|
+
>;
|
package/src/types/ai.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// AI assistant suggestion shapes. Suggestions are reviewable drafts — the
|
|
2
|
+
// backend stores them as AiSuggestion rows; applying one only fills the local
|
|
3
|
+
// edit form (or materializes through the subject model's normal endpoint).
|
|
4
|
+
|
|
5
|
+
export type AiSuggestionStatus =
|
|
6
|
+
| "pending"
|
|
7
|
+
| "ready"
|
|
8
|
+
| "applied"
|
|
9
|
+
| "dismissed"
|
|
10
|
+
| "failed";
|
|
11
|
+
|
|
12
|
+
export interface AiSuggestion<TPayload = unknown> {
|
|
13
|
+
type: "ai_suggestion";
|
|
14
|
+
id: number;
|
|
15
|
+
subject_type: string;
|
|
16
|
+
subject_id: number;
|
|
17
|
+
company_id: number | null;
|
|
18
|
+
suggestion_type: string;
|
|
19
|
+
locale: string;
|
|
20
|
+
payload: TPayload;
|
|
21
|
+
meta?: unknown;
|
|
22
|
+
status: AiSuggestionStatus;
|
|
23
|
+
requested_by: number | null;
|
|
24
|
+
applied_at: string | null;
|
|
25
|
+
dismissed_at: string | null;
|
|
26
|
+
created_at: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Course description assistant.
|
|
30
|
+
|
|
31
|
+
export interface AiSuggestedMilestone {
|
|
32
|
+
name: string;
|
|
33
|
+
description?: string | null;
|
|
34
|
+
kind: "progress" | "prerequisite" | "solo" | "cross_country";
|
|
35
|
+
is_prerequisite: boolean;
|
|
36
|
+
sort_order: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CourseDescriptionSuggestionPayload {
|
|
40
|
+
title: string;
|
|
41
|
+
subtitle: string;
|
|
42
|
+
summary: string;
|
|
43
|
+
html: string;
|
|
44
|
+
milestones: AiSuggestedMilestone[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// MIA logbook import (booking-scoped flight-log suggestions).
|
|
48
|
+
|
|
49
|
+
export interface LogbookFlightLogPayload {
|
|
50
|
+
etdZ: string | null;
|
|
51
|
+
etaZ: string | null;
|
|
52
|
+
departure_airport_id: number | null;
|
|
53
|
+
destination_airport_id: number | null;
|
|
54
|
+
departure_airport_icao: string | null;
|
|
55
|
+
destination_airport_icao: string | null;
|
|
56
|
+
full_stop_landings: number;
|
|
57
|
+
touch_and_gos: number;
|
|
58
|
+
low_approaches: number;
|
|
59
|
+
crew_assignments: { membership_id: number; role: string }[];
|
|
60
|
+
membership_id: number | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface LogbookSuggestionMeta {
|
|
64
|
+
flags?: string[];
|
|
65
|
+
match?: { confidence: string; reasoning: string };
|
|
66
|
+
extraction?: { valid: boolean | null; discrepancy_minutes: number | null };
|
|
67
|
+
crew_text?: string | null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type LogbookSuggestion = AiSuggestion<LogbookFlightLogPayload> & {
|
|
71
|
+
meta?: LogbookSuggestionMeta;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export interface DiscardedRow {
|
|
75
|
+
reason: string;
|
|
76
|
+
flight: Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface LogbookStageResult {
|
|
80
|
+
booking_id: number;
|
|
81
|
+
suggestion_ids: number[];
|
|
82
|
+
staged: number;
|
|
83
|
+
routed: Record<string, number> | number[];
|
|
84
|
+
discarded: DiscardedRow[];
|
|
85
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type AircraftBlockReason =
|
|
2
|
+
| "general"
|
|
3
|
+
| "ferry"
|
|
4
|
+
| "weather"
|
|
5
|
+
| "private"
|
|
6
|
+
| "aog"
|
|
7
|
+
| "other";
|
|
8
|
+
|
|
9
|
+
export interface AircraftBlock {
|
|
10
|
+
type: "aircraftBlock";
|
|
11
|
+
id: number;
|
|
12
|
+
airplane_id: number;
|
|
13
|
+
company_id: number;
|
|
14
|
+
starts_at: string; // UTC ISO
|
|
15
|
+
ends_at: string; // UTC ISO
|
|
16
|
+
reason: AircraftBlockReason;
|
|
17
|
+
note: string | null;
|
|
18
|
+
is_maintenance: boolean;
|
|
19
|
+
maintenance_event_id: number | null;
|
|
20
|
+
created_at: string;
|
|
21
|
+
updated_at: string;
|
|
22
|
+
can?: Record<string, boolean | null>;
|
|
23
|
+
}
|
package/src/types/api.ts
CHANGED
|
@@ -8,7 +8,9 @@ export interface DataWrappedResponse<T, M = undefined> {
|
|
|
8
8
|
meta?: M;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export
|
|
11
|
+
export type ApiResponse<T> = DataWrappedResponse<T>;
|
|
12
|
+
|
|
13
|
+
export interface PaginatedResponse<T = unknown> {
|
|
12
14
|
data: T[];
|
|
13
15
|
meta: {
|
|
14
16
|
current_page: number;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Generic background-task tracking. A long-running server action returns a
|
|
2
|
+
// BackgroundTask immediately (202); the client then tracks it to completion
|
|
3
|
+
// via the Echo `background_task` signal (push) with a polling fallback.
|
|
4
|
+
|
|
5
|
+
export type BackgroundTaskStatus =
|
|
6
|
+
| "queued"
|
|
7
|
+
| "processing"
|
|
8
|
+
| "completed"
|
|
9
|
+
| "failed"
|
|
10
|
+
| "canceled";
|
|
11
|
+
|
|
12
|
+
export interface BackgroundTask<TResult = unknown> {
|
|
13
|
+
type: "background_task";
|
|
14
|
+
/** Store key — mirrors `uuid` (the API has no client-facing integer id). */
|
|
15
|
+
id: string;
|
|
16
|
+
uuid: string;
|
|
17
|
+
task_type: string;
|
|
18
|
+
status: BackgroundTaskStatus;
|
|
19
|
+
progress: number | null;
|
|
20
|
+
progress_label: string | null;
|
|
21
|
+
company_id: number | null;
|
|
22
|
+
user_id: number | null;
|
|
23
|
+
result: TResult | null;
|
|
24
|
+
error: string | null;
|
|
25
|
+
started_at: string | null;
|
|
26
|
+
finished_at: string | null;
|
|
27
|
+
created_at: string | null;
|
|
28
|
+
}
|