@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.
- package/dist/chunk-AKLFMP7G.cjs +2525 -0
- package/dist/chunk-YCIXOVEC.js +2525 -0
- package/dist/index.cjs +193 -3
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +196 -6
- package/dist/schemas/index.cjs +192 -2
- package/dist/schemas/index.d.cts +3123 -94
- package/dist/schemas/index.d.ts +3123 -94
- package/dist/schemas/index.js +195 -5
- package/dist/{models-Bc5BKYuI.d.cts → training-Der1DPU-.d.cts} +17563 -1056
- package/dist/{models-Bc5BKYuI.d.ts → training-Der1DPU-.d.ts} +17563 -1056
- package/dist/types/index.cjs +1 -1
- package/dist/types/index.d.cts +275 -6
- package/dist/types/index.d.ts +275 -6
- package/dist/types/index.js +1 -1
- package/package.json +1 -1
- package/src/index.ts +39 -0
- package/src/schemas/common.ts +33 -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 +38 -20
- 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 +43 -3
- package/src/types/roles.ts +26 -0
- package/dist/chunk-RC2BBT6H.cjs +0 -1384
- package/dist/chunk-WQLDSZNN.js +0 -1384
- 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
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";
|
package/src/schemas/common.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/schemas/index.ts
CHANGED
|
@@ -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";
|