@updraft-solutions/mcrit-sdk 0.4.0 → 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.
Files changed (42) hide show
  1. package/dist/chunk-AKLFMP7G.cjs +2525 -0
  2. package/dist/chunk-YCIXOVEC.js +2525 -0
  3. package/dist/index.cjs +193 -3
  4. package/dist/index.d.cts +3 -3
  5. package/dist/index.d.ts +3 -3
  6. package/dist/index.js +196 -6
  7. package/dist/schemas/index.cjs +192 -2
  8. package/dist/schemas/index.d.cts +3123 -94
  9. package/dist/schemas/index.d.ts +3123 -94
  10. package/dist/schemas/index.js +195 -5
  11. package/dist/{models-Bc5BKYuI.d.cts → training-Der1DPU-.d.cts} +17563 -1056
  12. package/dist/{models-Bc5BKYuI.d.ts → training-Der1DPU-.d.ts} +17563 -1056
  13. package/dist/types/index.cjs +1 -1
  14. package/dist/types/index.d.cts +275 -6
  15. package/dist/types/index.d.ts +275 -6
  16. package/dist/types/index.js +1 -1
  17. package/package.json +1 -1
  18. package/src/index.ts +39 -0
  19. package/src/schemas/common.ts +33 -0
  20. package/src/schemas/course-milestone.ts +78 -0
  21. package/src/schemas/equipment.ts +27 -0
  22. package/src/schemas/form-template.ts +413 -0
  23. package/src/schemas/index.ts +7 -1
  24. package/src/schemas/location-map.ts +175 -0
  25. package/src/schemas/models.ts +38 -20
  26. package/src/schemas/newsletter.ts +362 -0
  27. package/src/schemas/todo.ts +46 -0
  28. package/src/schemas/training.ts +500 -0
  29. package/src/types/ai.ts +85 -0
  30. package/src/types/aircraft-block.ts +23 -0
  31. package/src/types/api.ts +3 -1
  32. package/src/types/background-task.ts +28 -0
  33. package/src/types/compliance.ts +27 -3
  34. package/src/types/index.ts +5 -0
  35. package/src/types/landing-fee.ts +92 -0
  36. package/src/types/models.ts +43 -3
  37. package/src/types/roles.ts +26 -0
  38. package/dist/chunk-RC2BBT6H.cjs +0 -1384
  39. package/dist/chunk-WQLDSZNN.js +0 -1384
  40. package/src/schemas/fee.ts +0 -37
  41. /package/dist/{chunk-LXNCAKJZ.js → chunk-2WJHTRIE.js} +0 -0
  42. /package/dist/{chunk-MOOGM3DW.cjs → chunk-DCEOET63.cjs} +0 -0
package/src/index.ts CHANGED
@@ -11,6 +11,9 @@ export {
11
11
  type AirplanePermission,
12
12
  type AirplaneShareInvite,
13
13
  type LandingFee,
14
+ type Fee,
15
+ type FeeCategoryOptions,
16
+ type Tag,
14
17
  type AircraftRating,
15
18
  type UserAircraftRating,
16
19
  type UserQualification,
@@ -48,6 +51,12 @@ export {
48
51
  type Event,
49
52
  type Ticket,
50
53
  type Post,
54
+ type CreatePostInput,
55
+ type UpdatePostInput,
56
+ type CreateAirplaneIssueInput,
57
+ type UpdateAirplaneIssueInput,
58
+ type ResolveAirplaneIssueInput,
59
+ type DeferAirplaneIssueInput,
51
60
  type Comment,
52
61
  type CreateCommentInput,
53
62
  type UpdateCommentInput,
@@ -57,6 +66,7 @@ export {
57
66
  type Setting,
58
67
  // types/api.ts
59
68
  type DataWrappedResponse,
69
+ type ApiResponse,
60
70
  type PaginatedResponse,
61
71
  type Resource,
62
72
  type ToggleFavoriteResponse,
@@ -64,9 +74,38 @@ export {
64
74
  type ComplianceRequirement,
65
75
  type ComplianceRequirementGroup,
66
76
  type ComplianceGroup,
77
+ type ComplianceGroupSource,
67
78
  type ComplianceSummary,
68
79
  type ComplianceData,
69
80
  type ComplianceResponse,
81
+ // types/aircraft-block.ts
82
+ type AircraftBlock,
83
+ type AircraftBlockReason,
84
+ // types/background-task.ts
85
+ type BackgroundTask,
86
+ type BackgroundTaskStatus,
87
+ // types/ai.ts
88
+ type AiSuggestion,
89
+ type AiSuggestionStatus,
90
+ type AiSuggestedMilestone,
91
+ type CourseDescriptionSuggestionPayload,
92
+ type LogbookFlightLogPayload,
93
+ type LogbookSuggestionMeta,
94
+ type LogbookSuggestion,
95
+ type DiscardedRow,
96
+ type LogbookStageResult,
97
+ // types/roles.ts
98
+ type RoleListItem,
99
+ type PermissionItem,
100
+ type RoleDetail,
101
+ type GroupedPermissions,
102
+ // types/landing-fee.ts
103
+ type LandingFeeTemplateParams,
104
+ type LandingFeeImportResult,
105
+ type LandingFeeInvoicePreviewRow,
106
+ type LandingFeeInvoicePreview,
107
+ type LandingFeeInvoiceApplyPayload,
108
+ type LandingFeeInvoiceApplyResult,
70
109
  } from "./types/index.js";
71
110
 
72
111
  export * from "./money/index.js";
@@ -57,12 +57,41 @@ export const mediaCollection = z
57
57
  .optional();
58
58
 
59
59
  // User & Company
60
+ /** Minimal participant shape used in the impersonation payload. */
61
+ export const impersonationParticipantSchema = z.object({
62
+ id: z.number(),
63
+ name: z.string(),
64
+ email: z.string(),
65
+ });
66
+ export type ImpersonationParticipant = z.infer<
67
+ typeof impersonationParticipantSchema
68
+ >;
69
+
70
+ /**
71
+ * Impersonation state. Rides along on the authenticated user payload
72
+ * (`/auth/user` → `user.impersonation`), so it is the single source of
73
+ * truth — no polling required. The backend's `HandleImpersonation`
74
+ * middleware also stamps an `X-Impersonating` header on every non-admin
75
+ * response, which clients use to detect a session that ended server-side.
76
+ */
77
+ export const impersonationStateSchema = z.object({
78
+ is_impersonating: z.boolean(),
79
+ impersonator: impersonationParticipantSchema.optional(),
80
+ impersonated: impersonationParticipantSchema.optional(),
81
+ started_at: z.string().optional(),
82
+ expires_at: z.string().optional(),
83
+ });
84
+ export type ImpersonationState = z.infer<typeof impersonationStateSchema>;
85
+
60
86
  export const userSchema = z.object({
61
87
  type: z.literal("user"),
62
88
  id: z.number(),
63
89
  name: z.string(),
64
90
  email: z.string().optional(),
65
91
  mobile: z.string().optional(),
92
+ // Present only on the authenticated-user payload while an admin
93
+ // impersonation session is active.
94
+ impersonation: impersonationStateSchema.optional(),
66
95
  });
67
96
 
68
97
  export const companySchema = z.object({
@@ -77,4 +106,8 @@ export const companySchema = z.object({
77
106
  // slim company payloads may omit it.
78
107
  timezone: z.string().optional(),
79
108
  ai_landing_fee_invoices_enabled: z.boolean().optional(),
109
+ // Active AI-feature entitlement keys (per-company subscription gating,
110
+ // e.g. "ai.logbook_import"). Serialized by CompanyResource alongside
111
+ // `permissions[]`; slim payloads may omit it.
112
+ entitlements: z.array(z.string()).optional(),
80
113
  });
@@ -0,0 +1,78 @@
1
+ import { z } from "zod";
2
+
3
+ export const CourseMilestoneKindEnum = z.enum([
4
+ "progress",
5
+ "solo",
6
+ "cross_country",
7
+ "theory",
8
+ "practical",
9
+ "prerequisite",
10
+ "custom",
11
+ ]);
12
+ export type CourseMilestoneKind = z.infer<typeof CourseMilestoneKindEnum>;
13
+
14
+ export const courseMilestoneSchema = z.object({
15
+ type: z.literal("courseMilestone").optional(),
16
+ id: z.number(),
17
+ course_id: z.number(),
18
+ name: z.string(),
19
+ description: z.string().nullable().optional(),
20
+ kind: z.string(),
21
+ is_prerequisite: z.boolean(),
22
+ requires_form_template_id: z.number().nullable().optional(),
23
+ required_form_template: z
24
+ .object({ id: z.number(), name: z.string().optional() })
25
+ .nullable()
26
+ .optional(),
27
+ completion_rule: z.record(z.string(), z.unknown()).nullable().optional(),
28
+ sort_order: z.number(),
29
+ created_at: z.string().optional(),
30
+ updated_at: z.string().optional(),
31
+ can: z
32
+ .object({
33
+ update: z.boolean().nullable().optional(),
34
+ delete: z.boolean().nullable().optional(),
35
+ })
36
+ .optional(),
37
+ });
38
+ export type CourseMilestone = z.infer<typeof courseMilestoneSchema>;
39
+
40
+ export const CourseMilestoneFormSchema = z.object({
41
+ name: z.string().min(2).max(191),
42
+ description: z.string().nullable().optional(),
43
+ kind: z.string().max(64).default("progress"),
44
+ is_prerequisite: z.boolean().default(false),
45
+ requires_form_template_id: z.number().nullable().optional(),
46
+ completion_rule: z.record(z.string(), z.unknown()).nullable().optional(),
47
+ sort_order: z.number().int().min(0).optional(),
48
+ });
49
+ export type CourseMilestoneForm = z.infer<typeof CourseMilestoneFormSchema>;
50
+
51
+ export const courseEnrollmentMilestoneSchema = z.object({
52
+ type: z.literal("courseEnrollmentMilestone").optional(),
53
+ id: z.number(),
54
+ course_enrollment_id: z.number(),
55
+ course_milestone_id: z.number(),
56
+ milestone: courseMilestoneSchema.optional(),
57
+ achieved_at: z.string().nullable().optional(),
58
+ achieved_by_membership_id: z.number().nullable().optional(),
59
+ achieved_by: z
60
+ .object({
61
+ id: z.number(),
62
+ user: z.object({ name: z.string().optional() }).optional(),
63
+ })
64
+ .nullable()
65
+ .optional(),
66
+ notes: z.string().nullable().optional(),
67
+ is_achieved: z.boolean(),
68
+ can_be_achieved: z.boolean(),
69
+ can: z
70
+ .object({
71
+ achieve: z.boolean().nullable().optional(),
72
+ revoke: z.boolean().nullable().optional(),
73
+ })
74
+ .optional(),
75
+ });
76
+ export type CourseEnrollmentMilestone = z.infer<
77
+ typeof courseEnrollmentMilestoneSchema
78
+ >;
@@ -0,0 +1,27 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Company-scoped equipment catalog entry (m-crit-api
5
+ * docs/features/airplane-equipment.md). Assigned to airplanes via the
6
+ * airplane PATCH `equipment` array.
7
+ */
8
+ export const equipmentSchema = z.object({
9
+ type: z.literal("equipment").optional(),
10
+ id: z.number(),
11
+ name: z.string(),
12
+ description: z.string().nullable().optional(),
13
+ manual_url: z.string().nullable().optional(),
14
+ });
15
+ export type Equipment = z.infer<typeof equipmentSchema>;
16
+
17
+ /**
18
+ * Write shape for the airplane PATCH: existing entries carry their id
19
+ * (description/manual_url on an id entry UPDATE the catalog entry);
20
+ * id-less entries are created on the fly by name.
21
+ */
22
+ export interface EquipmentEntry {
23
+ id?: number;
24
+ name: string;
25
+ description?: string | null;
26
+ manual_url?: string | null;
27
+ }
@@ -0,0 +1,413 @@
1
+ import { z } from "zod";
2
+
3
+ // Field types enum - matches backend FormFieldType
4
+ export enum FormFieldType {
5
+ Text = "text",
6
+ Textarea = "textarea",
7
+ Number = "number",
8
+ Date = "date",
9
+ Time = "time",
10
+ Duration = "duration",
11
+ Select = "select",
12
+ MultiSelect = "multi_select",
13
+ Checkbox = "checkbox",
14
+ Radio = "radio",
15
+ RatingScale = "rating_scale",
16
+ Signature = "signature",
17
+ PassFail = "pass_fail",
18
+ MembershipSelect = "membership_select",
19
+ InstructorSelect = "instructor_select",
20
+ AirplaneSelect = "airplane_select",
21
+ CourseSelect = "course_select",
22
+ MembershipSignature = "membership_signature",
23
+ FileUpload = "file_upload",
24
+ // Select with built-in mass unit options (kg/lbs/t) — see app/utils/massUnits.ts
25
+ MassUnitSelect = "mass_unit_select",
26
+ // Display-only text block (contract clauses, instructions). Carries its text in
27
+ // `content`, stores no value, never an input.
28
+ StaticText = "static_text",
29
+ }
30
+
31
+ // Submission status enum - matches backend FormSubmissionStatus
32
+ export enum FormSubmissionStatus {
33
+ Draft = "draft",
34
+ AwaitingSignatures = "awaiting_signatures",
35
+ Submitted = "submitted",
36
+ Approved = "approved",
37
+ Rejected = "rejected",
38
+ }
39
+
40
+ // Submission origin - matches backend FormSubmissionSource
41
+ export enum FormSubmissionSource {
42
+ Voluntary = "voluntary",
43
+ Onboarding = "onboarding",
44
+ AdHocRequest = "ad_hoc_request",
45
+ }
46
+
47
+ // Schema field label (i18n)
48
+ export const FieldLabelSchema = z.object({
49
+ de: z.string().optional(),
50
+ en: z.string(),
51
+ no: z.string().optional(),
52
+ });
53
+
54
+ // Conditional visibility
55
+ export const ShowWhenSchema = z.object({
56
+ field: z.string(),
57
+ operator: z.enum(["eq", "neq", "in", "not_in", "gt", "lt", "gte", "lte"]),
58
+ value: z.union([z.string(), z.number(), z.boolean(), z.array(z.string())]),
59
+ });
60
+
61
+ // Schema field definition
62
+ export const SchemaFieldSchema = z.object({
63
+ key: z.string(),
64
+ type: z.nativeEnum(FormFieldType),
65
+ // Optional because static_text fields use `content` instead of `label`.
66
+ label: FieldLabelSchema.optional(),
67
+ // Display-only text for static_text fields (i18n), rendered with line breaks.
68
+ content: FieldLabelSchema.optional(),
69
+ required: z.boolean().optional().default(false),
70
+ validation: z.record(z.any()).optional(),
71
+ options: z.record(z.any()).optional(),
72
+ col_span: z.number().min(1).max(12).optional(),
73
+ order: z.number(),
74
+ show_when: ShowWhenSchema.optional(),
75
+ // Signature fields only: auto-binds the designated signer to the membership_id stored in the
76
+ // referenced field (no manual designation needed).
77
+ signer_membership_field: z.string().optional(),
78
+ // Signature fields only: group designation — any member holding this permission at the
79
+ // template's company may sign (e.g. "formSubmission.signAsCompany" for the company signature).
80
+ signer_permission: z.string().optional(),
81
+ // Signature fields only: bind the signer to the membership the form is attached to
82
+ // ("submittable") — the person the form is about signs, with no picker.
83
+ signer_source: z.string().optional(),
84
+ // Text fields only: server-side party prefill (company.name|address,
85
+ // member.name|address). Resolved & frozen into data at issuance — render
86
+ // read-only; the backend rejects edits to these keys.
87
+ prefill_source: z.string().optional(),
88
+ });
89
+
90
+ // Schema section definition
91
+ export const SchemaSectionSchema = z.object({
92
+ key: z.string(),
93
+ title: z.string(),
94
+ order: z.number(),
95
+ fields: z.array(SchemaFieldSchema),
96
+ });
97
+
98
+ // Full template schema
99
+ export const TemplateSchemaSchema = z.object({
100
+ sections: z.array(SchemaSectionSchema),
101
+ });
102
+
103
+ // User basic schema for relationships
104
+ const UserBasicSchema = z
105
+ .object({
106
+ id: z.number(),
107
+ name: z.string(),
108
+ email: z.string().optional(),
109
+ })
110
+ .nullable();
111
+
112
+ // Form Template
113
+ export const FormTemplateSchema = z.object({
114
+ type: z.literal("form_template"),
115
+ id: z.number(),
116
+ company_id: z.number().nullable(),
117
+ name: z.string(),
118
+ slug: z.string(),
119
+ description: z.string().nullable(),
120
+ schema: TemplateSchemaSchema,
121
+ schema_version: z.number(),
122
+ pdf_view: z.string().nullable(),
123
+ requires_review: z.boolean(),
124
+ is_active: z.boolean(),
125
+ is_singleton: z.boolean().optional().default(false),
126
+ // Sign-only: submissions skip the draft phase (issued straight into
127
+ // awaiting_signatures) and auto-submit once all signatures are collected.
128
+ is_sign_only: z.boolean().optional().default(false),
129
+ // Requirable: this company template may be referenced by requirement groups
130
+ // (a compliance document), vs a workflow form. System templates are always
131
+ // requirable. Defaults false.
132
+ is_requirable: z.boolean().optional().default(false),
133
+ is_onboarding: z.boolean().optional().default(false),
134
+ // Array of system role keys this onboarding template applies to.
135
+ // null/missing = applies to every member at the company.
136
+ onboarding_role_keys: z.array(z.string()).nullable().optional(),
137
+ // System templates: platform-provided, super-admin maintained, no owning
138
+ // company. Cannot be assigned/filled until merge Phase 2.
139
+ is_system: z.boolean().optional().default(false),
140
+ // Scope of a system template: null/"user" = a personal qualification;
141
+ // "aircraft" = an aircraft document (e.g. weight report) excluded from the
142
+ // user qualification picker.
143
+ applies_to: z.string().nullable().optional(),
144
+ // When true, submissions carry an expires_at (content validity, e.g. a
145
+ // medical's expiry). Distinct from due_date (deadline to fill).
146
+ has_expiry: z.boolean().optional().default(false),
147
+ default_notification_days: z.number().nullable().optional(),
148
+ created_by: z.number().nullable(),
149
+ creator: UserBasicSchema.optional(),
150
+ assignments: z.array(z.any()).optional(),
151
+ created_at: z.string(),
152
+ updated_at: z.string(),
153
+ });
154
+
155
+ // Form Template Assignment
156
+ export const FormTemplateAssignmentSchema = z.object({
157
+ type: z.literal("form_template_assignment"),
158
+ id: z.number(),
159
+ form_template_id: z.number(),
160
+ assignable_type: z.string(),
161
+ assignable_id: z.number(),
162
+ target_type: z.string(),
163
+ // `trigger` is soft-deprecated — column kept as a future escape hatch but no
164
+ // handler reads it and the UI no longer surfaces it. See docs/features/forms.md.
165
+ trigger: z.string().nullable(),
166
+ sort_order: z.number(),
167
+ form_template: FormTemplateSchema.optional(),
168
+ created_at: z.string(),
169
+ updated_at: z.string(),
170
+ });
171
+
172
+ // Signature row from the polymorphic signatures table; each carries its own audit packet.
173
+ export const SignatureSchema = z.object({
174
+ type: z.literal("signature").optional(),
175
+ id: z.number(),
176
+ signable_type: z.string().optional(),
177
+ signable_id: z.number().optional(),
178
+ field_key: z.string(),
179
+ status: z.enum(["pending", "signed", "revoked"]),
180
+ required_signer_membership_id: z.number().nullable(),
181
+ required_signer_name: z.string().nullable().optional(),
182
+ signed_by_user_id: z.number().nullable(),
183
+ signed_by_membership_id: z.number().nullable(),
184
+ signed_by_name: z.string().nullable().optional(),
185
+ is_current_user_designated: z.boolean().optional().default(false),
186
+ signed_at: z.string().nullable(),
187
+ image: z.string().nullable(),
188
+ data_hash: z.string().nullable(),
189
+ schema_version: z.number().nullable(),
190
+ schema_hash: z.string().nullable(),
191
+ consent_text_hash: z.string().nullable(),
192
+ auth_method: z.string().nullable(),
193
+ auth_verified_at: z.string().nullable(),
194
+ ip_address: z.string().nullable(),
195
+ user_agent: z.string().nullable(),
196
+ geo_country: z.string().nullable(),
197
+ signing_event_id: z.string().nullable(),
198
+ created_at: z.string().optional(),
199
+ updated_at: z.string().optional(),
200
+ });
201
+
202
+ // Spatie MediaResource subset used by file_upload fields.
203
+ export const SubmissionFileSchema = z.object({
204
+ id: z.number(),
205
+ file_name: z.string(),
206
+ size: z.number(),
207
+ mime_type: z.string().nullable().optional(),
208
+ url: z.string().nullable().optional(),
209
+ conversions: z.record(z.string(), z.string().nullable()).optional(),
210
+ });
211
+
212
+ // Form Submission Share — a user-owned submission shared with a company
213
+ // (replaces the legacy document-shares model in merge Phase 3b). Base shape
214
+ // without the nested submission so it can be embedded on FormSubmission
215
+ // itself (`shares` — the owner sees all of them on their own listing).
216
+ export const FormSubmissionShareBaseSchema = z.object({
217
+ type: z.literal("form_submission_share").optional(),
218
+ id: z.number(),
219
+ form_submission_id: z.number(),
220
+ company_id: z.number(),
221
+ shared_by: z.number(),
222
+ company: z
223
+ .object({
224
+ id: z.number(),
225
+ name: z.string(),
226
+ })
227
+ .optional(),
228
+ created_at: z.string(),
229
+ });
230
+
231
+ // Form Submission
232
+ export const FormSubmissionSchema = z.object({
233
+ type: z.literal("form_submission"),
234
+ id: z.number(),
235
+ form_template_id: z.number(),
236
+ schema_version: z.number(),
237
+ schema_snapshot: TemplateSchemaSchema,
238
+ submittable_type: z.string(),
239
+ submittable_id: z.number(),
240
+ // Who/what the submission is about — shown in the dialog header. Present
241
+ // when the backend loaded the submittable relation.
242
+ submittable_summary: z
243
+ .object({
244
+ type: z.string(),
245
+ id: z.number(),
246
+ name: z.string().nullable(),
247
+ email: z.string().nullable(),
248
+ })
249
+ .nullable()
250
+ .optional(),
251
+ submitted_by: z.number(),
252
+ data: z.record(z.any()),
253
+ data_resolved_names: z.record(z.string(), z.string()).optional().default({}),
254
+ required_signers: z.record(z.string(), z.number()).nullable().optional(),
255
+ signatures: z.array(SignatureSchema).optional(),
256
+ status: z.nativeEnum(FormSubmissionStatus),
257
+ submitted_at: z.string().nullable(),
258
+ reviewed_by: z.number().nullable(),
259
+ reviewed_at: z.string().nullable(),
260
+ review_comment: z.string().nullable(),
261
+ is_final: z.boolean().optional().default(false),
262
+ is_mutable: z.boolean().optional().default(false),
263
+ is_signable: z.boolean().optional().default(false),
264
+ has_signature_fields: z.boolean().optional().default(false),
265
+ all_signatures_collected: z.boolean().optional().default(true),
266
+ // Server-evaluated policy decisions per gate (delete, revertToDraft, update, …),
267
+ // read via hasModelPermission(submission, '<gate>'). Null when the fetch didn't
268
+ // request permissions; populated when it passes permissions[form_submission].
269
+ can: z.record(z.boolean().nullable()).optional().default({}),
270
+ // Request envelope for pre-issued drafts (onboarding / ad-hoc admin request).
271
+ // Voluntary submissions leave note/due_date/requester null.
272
+ source: z
273
+ .nativeEnum(FormSubmissionSource)
274
+ .optional()
275
+ .default(FormSubmissionSource.Voluntary),
276
+ due_date: z.string().nullable().optional(),
277
+ note: z.string().nullable().optional(),
278
+ requested_by_user_id: z.number().nullable().optional(),
279
+ is_overdue: z.boolean().optional().default(false),
280
+ days_until_due: z.number().nullable().optional(),
281
+ // Content validity (has_expiry templates only). ISO datetime.
282
+ expires_at: z.string().nullable().optional(),
283
+ notification_days_before: z.number().nullable().optional(),
284
+ notifications_enabled: z.boolean().optional().default(true),
285
+ is_expired: z.boolean().optional().default(false),
286
+ // { field_key: media | null } for every file_upload field in the snapshot.
287
+ files: z.record(z.string(), SubmissionFileSchema.nullable()).optional(),
288
+ form_template: FormTemplateSchema.optional(),
289
+ submitter: UserBasicSchema.optional(),
290
+ reviewer: UserBasicSchema.optional(),
291
+ requester: UserBasicSchema.optional(),
292
+ // Shares of this (user-owned) submission. Present on listings that
293
+ // eager-load them — the owner sees all, recipient staff only their own
294
+ // companies' rows (viewer-scoped in the backend resource).
295
+ shares: z.array(FormSubmissionShareBaseSchema).optional(),
296
+ created_at: z.string(),
297
+ updated_at: z.string(),
298
+ });
299
+
300
+ // Full share shape — `form_submission` is nested on the my-shares /
301
+ // company-inbox listings.
302
+ export const FormSubmissionShareSchema = FormSubmissionShareBaseSchema.extend({
303
+ form_submission: FormSubmissionSchema.optional(),
304
+ });
305
+
306
+ // Cross-link source metadata. Present on rows surfaced through the
307
+ // enrollment ← booking link; null when the row belongs to the queried
308
+ // submittable directly. See backend GetRequiredFormsForModel.
309
+ // Full crew of the source booking, incl. virtual roles (authorizing
310
+ // instructor) — role values are backend CrewMemberPosition snake_case.
311
+ export const RequiredFormViaCrewSchema = z.object({
312
+ name: z.string().nullable(),
313
+ role: z.string(),
314
+ is_pic: z.boolean(),
315
+ });
316
+
317
+ export const RequiredFormViaSchema = z.object({
318
+ type: z.literal("booking"),
319
+ booking_id: z.number(),
320
+ etdZ: z.string().nullable(),
321
+ route_display: z.string().nullable(),
322
+ airplane_registration: z.string().nullable(),
323
+ crew: z.array(RequiredFormViaCrewSchema).optional().default([]),
324
+ });
325
+
326
+ // Required form (convenience endpoint response)
327
+ export const RequiredFormSchema = z.object({
328
+ template: FormTemplateSchema,
329
+ assignment: FormTemplateAssignmentSchema,
330
+ // Latest submission (back-compat for callers that haven't migrated to the array).
331
+ submission: FormSubmissionSchema.nullable(),
332
+ // All submissions for this (template, submittable), newest first. For singleton templates
333
+ // length is 0 or 1; non-singleton templates can grow over time.
334
+ submissions: z.array(FormSubmissionSchema).optional().default([]),
335
+ is_complete: z.boolean(),
336
+ via: RequiredFormViaSchema.nullable().optional(),
337
+ });
338
+
339
+ // Type exports
340
+ export type FieldLabel = z.infer<typeof FieldLabelSchema>;
341
+ export type ShowWhen = z.infer<typeof ShowWhenSchema>;
342
+ export type SchemaField = z.infer<typeof SchemaFieldSchema>;
343
+ export type SchemaSection = z.infer<typeof SchemaSectionSchema>;
344
+ export type TemplateSchema = z.infer<typeof TemplateSchemaSchema>;
345
+ export type FormTemplate = z.infer<typeof FormTemplateSchema>;
346
+ export type FormTemplateAssignment = z.infer<
347
+ typeof FormTemplateAssignmentSchema
348
+ >;
349
+ export type FormSubmission = z.infer<typeof FormSubmissionSchema>;
350
+ export type FormSubmissionShare = z.infer<typeof FormSubmissionShareSchema>;
351
+ export type FormSubmissionShareBase = z.infer<
352
+ typeof FormSubmissionShareBaseSchema
353
+ >;
354
+ export type SubmissionFile = z.infer<typeof SubmissionFileSchema>;
355
+ export type RequiredForm = z.infer<typeof RequiredFormSchema>;
356
+ export type RequiredFormVia = z.infer<typeof RequiredFormViaSchema>;
357
+ export type RequiredFormViaCrew = z.infer<typeof RequiredFormViaCrewSchema>;
358
+ export type Signature = z.infer<typeof SignatureSchema>;
359
+
360
+ // Input types
361
+ export interface CreateFormTemplateInput {
362
+ name: string;
363
+ slug?: string;
364
+ description?: string | null;
365
+ schema: TemplateSchema;
366
+ pdf_view?: string | null;
367
+ requires_review?: boolean;
368
+ is_active?: boolean;
369
+ is_singleton?: boolean;
370
+ is_sign_only?: boolean;
371
+ is_onboarding?: boolean;
372
+ onboarding_role_keys?: string[] | null;
373
+ has_expiry?: boolean;
374
+ default_notification_days?: number | null;
375
+ }
376
+
377
+ export interface UpdateFormTemplateInput {
378
+ name?: string;
379
+ slug?: string;
380
+ description?: string | null;
381
+ schema?: TemplateSchema;
382
+ pdf_view?: string | null;
383
+ requires_review?: boolean;
384
+ is_active?: boolean;
385
+ is_singleton?: boolean;
386
+ is_sign_only?: boolean;
387
+ is_onboarding?: boolean;
388
+ onboarding_role_keys?: string[] | null;
389
+ has_expiry?: boolean;
390
+ default_notification_days?: number | null;
391
+ }
392
+
393
+ export interface CreateFormTemplateAssignmentInput {
394
+ assignable_type: "course" | "company";
395
+ assignable_id: number;
396
+ target_type: "course_enrollment" | "booking" | "membership";
397
+ // @deprecated trigger is accepted by the backend for back-compat but no
398
+ // handler reads it. UI does not send it. Kept on the input shape only.
399
+ trigger?: "on_completion" | "on_creation" | "before_solo" | "manual" | null;
400
+ sort_order?: number;
401
+ }
402
+
403
+ export interface CreateFormSubmissionInput {
404
+ form_template_id: number;
405
+ data: Record<string, unknown>;
406
+ expires_at?: string | null;
407
+ notification_days_before?: number | null;
408
+ }
409
+
410
+ export interface ReviewFormSubmissionInput {
411
+ decision: "approved" | "rejected";
412
+ review_comment?: string | null;
413
+ }
@@ -1,6 +1,12 @@
1
1
  export * from "./common";
2
2
  export * from "./models";
3
3
  export * from "./faq";
4
- export * from "./fee";
5
4
  export * from "./safety-report";
6
5
  export * from "./report";
6
+ export * from "./form-template";
7
+ export * from "./training";
8
+ export * from "./course-milestone";
9
+ export * from "./equipment";
10
+ export * from "./newsletter";
11
+ export * from "./location-map";
12
+ export * from "./todo";