@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
package/src/schemas/models.ts
CHANGED
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
zNetGross,
|
|
12
12
|
zPhpMoneyObject,
|
|
13
13
|
} from "./common";
|
|
14
|
+
import { equipmentSchema } from "./equipment";
|
|
15
|
+
import { courseCheckSyllabusSchema } from "./training";
|
|
14
16
|
|
|
15
17
|
export const baseMembershipSchema = z.object({
|
|
16
18
|
type: z.literal("membership"),
|
|
@@ -88,33 +90,58 @@ export const feeSchema = BaseModel.extend({
|
|
|
88
90
|
id: z.number(),
|
|
89
91
|
price: priceSchema.optional(),
|
|
90
92
|
prices: z.array(priceSchema).optional(),
|
|
91
|
-
provider: z.string().optional(),
|
|
93
|
+
provider: z.string().nullable().optional(),
|
|
92
94
|
company_id: z.number().optional(),
|
|
93
95
|
company: companySchema.optional(),
|
|
94
96
|
ident: z.string().optional(),
|
|
95
97
|
category: z.string().optional(),
|
|
96
|
-
subcategory: z.string().optional(),
|
|
98
|
+
subcategory: z.string().nullable().optional(),
|
|
97
99
|
title: z.string().optional(),
|
|
100
|
+
internal_title: z.string().nullable().optional(),
|
|
101
|
+
// internal_title falling back to title, resolved server-side.
|
|
102
|
+
display_title: z.string().optional(),
|
|
103
|
+
// Course-derived fees (source course + which course fee spawned them).
|
|
104
|
+
source_course_id: z.number().nullable().optional(),
|
|
105
|
+
source_key: z.string().nullable().optional(),
|
|
106
|
+
is_derived: z.boolean().optional(),
|
|
98
107
|
relation: z
|
|
99
108
|
.array(z.enum(["booking", "flightLog", "invoice", "instructor"]))
|
|
109
|
+
.nullable()
|
|
100
110
|
.optional(),
|
|
101
|
-
payment_per: z.string().optional(),
|
|
102
|
-
can: z
|
|
103
|
-
.object({
|
|
104
|
-
update: zNullableBool,
|
|
105
|
-
delete: zNullableBool,
|
|
106
|
-
})
|
|
107
|
-
.optional(),
|
|
111
|
+
payment_per: z.string().nullable().optional(),
|
|
112
|
+
can: z.record(z.string(), zNullableBool).optional(),
|
|
108
113
|
course_count: z.number().optional(),
|
|
114
|
+
// feeables-pivot fields, present when the fee is serialized through an
|
|
115
|
+
// attachment (course/enrollment listings).
|
|
109
116
|
count: z.number().optional(),
|
|
110
|
-
variant: z.string().optional(),
|
|
117
|
+
variant: z.string().nullable().optional(),
|
|
111
118
|
is_optional: z.boolean().optional(),
|
|
119
|
+
sort_order: z.number().nullable().optional(),
|
|
120
|
+
is_initial_payment: z.boolean().optional(),
|
|
121
|
+
feeables: z
|
|
122
|
+
.array(
|
|
123
|
+
z.object({
|
|
124
|
+
feeable_id: z.number(),
|
|
125
|
+
feeable_type: z.string(),
|
|
126
|
+
count: z.number().nullable(),
|
|
127
|
+
variant: z.string().nullable(),
|
|
128
|
+
is_optional: z.boolean().nullable(),
|
|
129
|
+
is_initial_payment: z.boolean().nullable(),
|
|
130
|
+
sort_order: z.number().nullable(),
|
|
131
|
+
}),
|
|
132
|
+
)
|
|
133
|
+
.optional(),
|
|
112
134
|
input_value: z.number().optional(),
|
|
113
135
|
net_gross: zNetGross,
|
|
114
136
|
tax_percentage: z.string().optional(),
|
|
115
137
|
valid_from: z.string(),
|
|
116
138
|
});
|
|
117
139
|
|
|
140
|
+
export const feeCategoryOptionsSchema = z.object({
|
|
141
|
+
categories: z.array(z.string()),
|
|
142
|
+
subcategories: z.record(z.string(), z.array(z.string())),
|
|
143
|
+
});
|
|
144
|
+
|
|
118
145
|
export const courseFeeSchema = feeSchema.extend({
|
|
119
146
|
count: z.number().optional(),
|
|
120
147
|
variant: z.string().optional(),
|
|
@@ -265,11 +292,19 @@ export const userQualificationSchema = z.object({
|
|
|
265
292
|
});
|
|
266
293
|
|
|
267
294
|
export const airportSchema = z.object({
|
|
268
|
-
|
|
295
|
+
// Aligned with what AirportResource actually serializes: `type` and
|
|
296
|
+
// `country` are absent on minified payloads (e.g. booking waypoint
|
|
297
|
+
// airports); coordinates + municipality/iso_country ride along on the
|
|
298
|
+
// full shape.
|
|
299
|
+
type: z.literal("airport").optional(),
|
|
269
300
|
id: z.number(),
|
|
270
301
|
ident: z.string(),
|
|
271
302
|
name: z.string(),
|
|
272
|
-
country: z.string(),
|
|
303
|
+
country: z.string().optional(),
|
|
304
|
+
municipality: z.string().nullable().optional(),
|
|
305
|
+
iso_country: z.string().nullable().optional(),
|
|
306
|
+
latitude_deg: z.number().nullable().optional(),
|
|
307
|
+
longitude_deg: z.number().nullable().optional(),
|
|
273
308
|
});
|
|
274
309
|
|
|
275
310
|
export const recurringFlightSchema = z.object({
|
|
@@ -340,7 +375,7 @@ export const extendedAirplaneSchema = airplaneSchema.extend({
|
|
|
340
375
|
VFRNight: z.boolean().optional(),
|
|
341
376
|
IFR: z.boolean().optional(),
|
|
342
377
|
IFRKnownIcing: z.boolean().optional(),
|
|
343
|
-
equipment: z.array(
|
|
378
|
+
equipment: z.array(equipmentSchema).optional(),
|
|
344
379
|
contact: contactAttributeSchema.optional(),
|
|
345
380
|
hints: z
|
|
346
381
|
.object({
|
|
@@ -387,6 +422,26 @@ export const availSchema = z.object({
|
|
|
387
422
|
.optional(),
|
|
388
423
|
});
|
|
389
424
|
|
|
425
|
+
// Ordered via-airport on a booking's route (BookingResource `waypoints`).
|
|
426
|
+
// Airports arrive minified (no `type` literal), hence the dedicated shape.
|
|
427
|
+
// Distinct from the nav-fix `waypointSchema` further down — do not merge.
|
|
428
|
+
export const bookingWaypointSchema = z.object({
|
|
429
|
+
sequence: z.number(),
|
|
430
|
+
airport_id: z.number(),
|
|
431
|
+
airport: z
|
|
432
|
+
.object({
|
|
433
|
+
id: z.number(),
|
|
434
|
+
ident: z.string(),
|
|
435
|
+
name: z.string(),
|
|
436
|
+
municipality: z.string().nullable().optional(),
|
|
437
|
+
iso_country: z.string().nullable().optional(),
|
|
438
|
+
latitude_deg: z.number().nullable().optional(),
|
|
439
|
+
longitude_deg: z.number().nullable().optional(),
|
|
440
|
+
})
|
|
441
|
+
.nullable()
|
|
442
|
+
.optional(),
|
|
443
|
+
});
|
|
444
|
+
|
|
390
445
|
export const bookingSchema = BaseModel.extend({
|
|
391
446
|
type: z.literal("booking"),
|
|
392
447
|
ifr: z.boolean().default(false),
|
|
@@ -403,6 +458,15 @@ export const bookingSchema = BaseModel.extend({
|
|
|
403
458
|
destination_airport_id: z.number().optional(),
|
|
404
459
|
departure_airport: airportSchema.optional(),
|
|
405
460
|
destination_airport: airportSchema.optional(),
|
|
461
|
+
waypoints: z.array(bookingWaypointSchema).optional(),
|
|
462
|
+
// Flat, always-present home timezone on BookingResource.
|
|
463
|
+
company_timezone: z.string().nullable().optional(),
|
|
464
|
+
// Slim per-booking training-record status, present when the backend
|
|
465
|
+
// eager-loaded `trainingRecord` (enrollment bookings surfaces).
|
|
466
|
+
training_record_status: z
|
|
467
|
+
.enum(["draft", "awaiting_signatures", "signed"])
|
|
468
|
+
.nullable()
|
|
469
|
+
.optional(),
|
|
406
470
|
next_booking_id: z.number().nullable().optional(),
|
|
407
471
|
previous_booking_id: z.number().nullable().optional(),
|
|
408
472
|
maintenance: z.number().optional(),
|
|
@@ -797,6 +861,15 @@ export const expenseSchema = z.object({
|
|
|
797
861
|
.optional(),
|
|
798
862
|
});
|
|
799
863
|
|
|
864
|
+
// Company-scoped tag (shared pool across taggable models — see
|
|
865
|
+
// m-crit-api docs/features/tags.md).
|
|
866
|
+
export const tagSchema = z.object({
|
|
867
|
+
type: z.literal("tag").optional(),
|
|
868
|
+
id: z.number(),
|
|
869
|
+
name: z.string(),
|
|
870
|
+
color: z.string().nullable().optional(),
|
|
871
|
+
});
|
|
872
|
+
|
|
800
873
|
export const courseSchema = z.object({
|
|
801
874
|
type: z.literal("course"),
|
|
802
875
|
id: z.number(),
|
|
@@ -813,15 +886,25 @@ export const courseSchema = z.object({
|
|
|
813
886
|
aircraft_required: z.boolean().default(false),
|
|
814
887
|
qualification_id: z.number().nullable().optional(),
|
|
815
888
|
qualification: qualificationSchema.optional(),
|
|
889
|
+
is_archived: z.boolean().optional(),
|
|
890
|
+
archived_at: z.string().nullable().optional(),
|
|
891
|
+
category: z.string().nullable().optional(),
|
|
892
|
+
title: z.string().nullable().optional(),
|
|
893
|
+
subtitle: z.string().nullable().optional(),
|
|
894
|
+
content: z.string().nullable().optional(),
|
|
895
|
+
footer: z.string().nullable().optional(),
|
|
896
|
+
// Syllabus assignment pointers: `syllabus_id` drives the Übersicht,
|
|
897
|
+
// `booking_form_syllabus_id` the per-flight Ausbildungsnachweis.
|
|
898
|
+
syllabus_id: z.number().nullable().optional(),
|
|
899
|
+
booking_form_syllabus_id: z.number().nullable().optional(),
|
|
900
|
+
// Present only when the backend eager-loaded `checkSyllabi`.
|
|
901
|
+
check_syllabi: z.array(courseCheckSyllabusSchema).optional(),
|
|
902
|
+
tags: z.array(tagSchema).optional(),
|
|
816
903
|
created_at: z.string(),
|
|
817
904
|
updated_at: z.string(),
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
delete: zNullableBool,
|
|
822
|
-
enroll: zNullableBool,
|
|
823
|
-
})
|
|
824
|
-
.optional(),
|
|
905
|
+
// Loose record on purpose: gates are policy-reflected server-side and
|
|
906
|
+
// grow over time (useAssistant, …) — same convention as companySchema.
|
|
907
|
+
can: z.record(z.string(), zNullableBool).optional(),
|
|
825
908
|
});
|
|
826
909
|
|
|
827
910
|
/**
|
|
@@ -854,20 +937,19 @@ export const courseEnrollmentSchema = z.object({
|
|
|
854
937
|
comments: z.string().nullable().optional(),
|
|
855
938
|
initial_payment_generated: z.boolean().optional(),
|
|
856
939
|
initial_payment_removable: z.boolean().optional(),
|
|
940
|
+
// Element-driven training progress (see m-crit-api
|
|
941
|
+
// docs/features/training-tracking.md).
|
|
942
|
+
tracked_training_elements_count: z.number().optional(),
|
|
943
|
+
progress_source: z.enum(["training_elements", "milestones"]).optional(),
|
|
857
944
|
metrics: courseEnrollmentMetricsSchema.optional(),
|
|
858
945
|
created_at: z.string(),
|
|
859
946
|
updated_at: z.string(),
|
|
860
947
|
course: courseSchema.optional(),
|
|
861
948
|
membership: membershipSchema.optional(),
|
|
862
949
|
mentor: userSchema.optional(),
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
update: zNullableBool,
|
|
867
|
-
delete: zNullableBool,
|
|
868
|
-
complete: zNullableBool,
|
|
869
|
-
})
|
|
870
|
-
.optional(),
|
|
950
|
+
// Loose record on purpose: gates are policy-reflected server-side and
|
|
951
|
+
// grow over time (updateTraining, manageInitialPayment, …).
|
|
952
|
+
can: z.record(z.string(), zNullableBool).optional(),
|
|
871
953
|
});
|
|
872
954
|
|
|
873
955
|
export const eventSchema = z.object({
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
|------------------------------------------------------------------------
|
|
5
|
+
| Block schemas
|
|
6
|
+
|------------------------------------------------------------------------
|
|
7
|
+
|
|
|
8
|
+
| v1 supports heading / paragraph / button / divider. Paragraph content
|
|
9
|
+
| is stored as TipTap JSON on `doc` so variable tokens survive
|
|
10
|
+
| sanitisation. The server converts `variable` nodes to `{{name}}` at
|
|
11
|
+
| render time.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export enum NewsletterBlockType {
|
|
15
|
+
Heading = "heading",
|
|
16
|
+
Paragraph = "paragraph",
|
|
17
|
+
Button = "button",
|
|
18
|
+
Divider = "divider",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const TipTapNodeSchema: z.ZodType<TipTapNode> = z.lazy(() =>
|
|
22
|
+
z.object({
|
|
23
|
+
type: z.string(),
|
|
24
|
+
attrs: z.record(z.any()).optional(),
|
|
25
|
+
content: z.array(TipTapNodeSchema).optional(),
|
|
26
|
+
marks: z.array(z.record(z.any())).optional(),
|
|
27
|
+
text: z.string().optional(),
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
export interface TipTapNode {
|
|
32
|
+
type: string;
|
|
33
|
+
attrs?: Record<string, unknown>;
|
|
34
|
+
content?: TipTapNode[];
|
|
35
|
+
marks?: Array<Record<string, unknown>>;
|
|
36
|
+
text?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const Align = z.enum(["left", "center", "right"]).optional();
|
|
40
|
+
|
|
41
|
+
export const HeadingBlockSchema = z.object({
|
|
42
|
+
key: z.string(),
|
|
43
|
+
type: z.literal(NewsletterBlockType.Heading),
|
|
44
|
+
level: z.number().int().min(1).max(3).default(1),
|
|
45
|
+
text: z.string(),
|
|
46
|
+
align: Align,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export const ParagraphBlockSchema = z.object({
|
|
50
|
+
key: z.string(),
|
|
51
|
+
type: z.literal(NewsletterBlockType.Paragraph),
|
|
52
|
+
doc: TipTapNodeSchema,
|
|
53
|
+
align: Align,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export const ButtonBlockSchema = z.object({
|
|
57
|
+
key: z.string(),
|
|
58
|
+
type: z.literal(NewsletterBlockType.Button),
|
|
59
|
+
label: z.string(),
|
|
60
|
+
url: z.string(),
|
|
61
|
+
align: Align,
|
|
62
|
+
color: z.string().optional(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export const DividerBlockSchema = z.object({
|
|
66
|
+
key: z.string(),
|
|
67
|
+
type: z.literal(NewsletterBlockType.Divider),
|
|
68
|
+
thickness: z.number().int().min(1).max(10).optional(),
|
|
69
|
+
color: z.string().optional(),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export const NewsletterBlockSchema = z.discriminatedUnion("type", [
|
|
73
|
+
HeadingBlockSchema,
|
|
74
|
+
ParagraphBlockSchema,
|
|
75
|
+
ButtonBlockSchema,
|
|
76
|
+
DividerBlockSchema,
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
export type HeadingBlock = z.infer<typeof HeadingBlockSchema>;
|
|
80
|
+
export type ParagraphBlock = z.infer<typeof ParagraphBlockSchema>;
|
|
81
|
+
export type ButtonBlock = z.infer<typeof ButtonBlockSchema>;
|
|
82
|
+
export type DividerBlock = z.infer<typeof DividerBlockSchema>;
|
|
83
|
+
export type NewsletterBlock = z.infer<typeof NewsletterBlockSchema>;
|
|
84
|
+
|
|
85
|
+
/*
|
|
86
|
+
|------------------------------------------------------------------------
|
|
87
|
+
| Category
|
|
88
|
+
|------------------------------------------------------------------------
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
export const NewsletterCategorySchema = z.object({
|
|
92
|
+
type: z.literal("newsletter_category").optional(),
|
|
93
|
+
id: z.number(),
|
|
94
|
+
key: z.string(),
|
|
95
|
+
name: z.string(),
|
|
96
|
+
description: z.string().nullable().optional(),
|
|
97
|
+
is_mandatory: z.boolean(),
|
|
98
|
+
is_active: z.boolean(),
|
|
99
|
+
sort_order: z.number().int(),
|
|
100
|
+
created_at: z.string().optional(),
|
|
101
|
+
updated_at: z.string().optional(),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
export type NewsletterCategory = z.infer<typeof NewsletterCategorySchema>;
|
|
105
|
+
|
|
106
|
+
/*
|
|
107
|
+
|------------------------------------------------------------------------
|
|
108
|
+
| Template
|
|
109
|
+
|------------------------------------------------------------------------
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
const UserBasicSchema = z
|
|
113
|
+
.object({
|
|
114
|
+
id: z.number(),
|
|
115
|
+
name: z.string(),
|
|
116
|
+
email: z.string().optional(),
|
|
117
|
+
})
|
|
118
|
+
.nullable();
|
|
119
|
+
|
|
120
|
+
export const NewsletterTemplateSchemaSchema = z.object({
|
|
121
|
+
blocks: z.array(NewsletterBlockSchema),
|
|
122
|
+
variables_used: z.array(z.string()).optional(),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
export type NewsletterTemplateDoc = z.infer<typeof NewsletterTemplateSchemaSchema>;
|
|
126
|
+
|
|
127
|
+
export const NewsletterTemplateSchema = z.object({
|
|
128
|
+
type: z.literal("newsletter_template").optional(),
|
|
129
|
+
id: z.number(),
|
|
130
|
+
company_id: z.number().nullable(),
|
|
131
|
+
is_system: z.boolean().optional(),
|
|
132
|
+
newsletter_category_id: z.number(),
|
|
133
|
+
category: NewsletterCategorySchema.nullish(),
|
|
134
|
+
name: z.string(),
|
|
135
|
+
slug: z.string(),
|
|
136
|
+
description: z.string().nullable().optional(),
|
|
137
|
+
subject: z.string(),
|
|
138
|
+
preheader: z.string().nullable().optional(),
|
|
139
|
+
schema: NewsletterTemplateSchemaSchema,
|
|
140
|
+
schema_version: z.number().int(),
|
|
141
|
+
is_active: z.boolean(),
|
|
142
|
+
created_by: z.number().nullable().optional(),
|
|
143
|
+
creator: UserBasicSchema.optional(),
|
|
144
|
+
created_at: z.string().optional(),
|
|
145
|
+
updated_at: z.string().optional(),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
export type NewsletterTemplate = z.infer<typeof NewsletterTemplateSchema>;
|
|
149
|
+
|
|
150
|
+
export const CreateNewsletterTemplateInputSchema = z.object({
|
|
151
|
+
newsletter_category_id: z.number(),
|
|
152
|
+
name: z.string().min(1),
|
|
153
|
+
slug: z.string().optional(),
|
|
154
|
+
description: z.string().nullable().optional(),
|
|
155
|
+
subject: z.string().min(1),
|
|
156
|
+
preheader: z.string().nullable().optional(),
|
|
157
|
+
schema: NewsletterTemplateSchemaSchema,
|
|
158
|
+
is_active: z.boolean().optional(),
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
export type CreateNewsletterTemplateInput = z.infer<typeof CreateNewsletterTemplateInputSchema>;
|
|
162
|
+
export type UpdateNewsletterTemplateInput = Partial<CreateNewsletterTemplateInput>;
|
|
163
|
+
|
|
164
|
+
/*
|
|
165
|
+
|------------------------------------------------------------------------
|
|
166
|
+
| Audience
|
|
167
|
+
|------------------------------------------------------------------------
|
|
168
|
+
*/
|
|
169
|
+
|
|
170
|
+
export enum AudienceBucketType {
|
|
171
|
+
AllUsers = "all_users",
|
|
172
|
+
CompanyUsers = "company_users",
|
|
173
|
+
RoleMembers = "role_members",
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export const AllUsersBucketSchema = z.object({ type: z.literal(AudienceBucketType.AllUsers) });
|
|
177
|
+
export const CompanyUsersBucketSchema = z.object({
|
|
178
|
+
type: z.literal(AudienceBucketType.CompanyUsers),
|
|
179
|
+
company_ids: z.array(z.number()).min(1),
|
|
180
|
+
});
|
|
181
|
+
export const RoleMembersBucketSchema = z.object({
|
|
182
|
+
type: z.literal(AudienceBucketType.RoleMembers),
|
|
183
|
+
role_ids: z.array(z.number()).min(1),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
export const AudienceBucketSchema = z.discriminatedUnion("type", [
|
|
187
|
+
AllUsersBucketSchema,
|
|
188
|
+
CompanyUsersBucketSchema,
|
|
189
|
+
RoleMembersBucketSchema,
|
|
190
|
+
]);
|
|
191
|
+
|
|
192
|
+
export const NewsletterAudienceSchema = z.object({
|
|
193
|
+
version: z.literal(1),
|
|
194
|
+
include: z.array(AudienceBucketSchema).min(1),
|
|
195
|
+
exclude: z
|
|
196
|
+
.object({
|
|
197
|
+
emails: z.array(z.string().email()).optional(),
|
|
198
|
+
user_ids: z.array(z.number()).optional(),
|
|
199
|
+
})
|
|
200
|
+
.optional(),
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
export type AudienceBucket = z.infer<typeof AudienceBucketSchema>;
|
|
204
|
+
export type NewsletterAudience = z.infer<typeof NewsletterAudienceSchema>;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Company-admin audience rule: the role selection is the sole determinant.
|
|
208
|
+
* No roles selected → the whole company (company_users). One or more roles
|
|
209
|
+
* → only those role-holders (role_members), and the company-wide bucket is
|
|
210
|
+
* dropped so the send-time union (ResolveCampaignAudienceAction) cannot
|
|
211
|
+
* widen the audience back to everyone.
|
|
212
|
+
*/
|
|
213
|
+
export function companyAudienceInclude(
|
|
214
|
+
companyId: number,
|
|
215
|
+
roleIds: number[],
|
|
216
|
+
): AudienceBucket[] {
|
|
217
|
+
return roleIds.length > 0
|
|
218
|
+
? [{ type: AudienceBucketType.RoleMembers, role_ids: roleIds }]
|
|
219
|
+
: [{ type: AudienceBucketType.CompanyUsers, company_ids: [companyId] }];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/*
|
|
223
|
+
|------------------------------------------------------------------------
|
|
224
|
+
| Campaign
|
|
225
|
+
|------------------------------------------------------------------------
|
|
226
|
+
*/
|
|
227
|
+
|
|
228
|
+
export enum NewsletterCampaignStatus {
|
|
229
|
+
Draft = "draft",
|
|
230
|
+
Scheduled = "scheduled",
|
|
231
|
+
Sending = "sending",
|
|
232
|
+
Sent = "sent",
|
|
233
|
+
Failed = "failed",
|
|
234
|
+
Cancelled = "cancelled",
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export const NewsletterCampaignSchema = z.object({
|
|
238
|
+
type: z.literal("newsletter_campaign").optional(),
|
|
239
|
+
id: z.number(),
|
|
240
|
+
company_id: z.number().nullable(),
|
|
241
|
+
is_system: z.boolean().optional(),
|
|
242
|
+
newsletter_template_id: z.number().nullable(),
|
|
243
|
+
template: NewsletterTemplateSchema.partial().nullish(),
|
|
244
|
+
newsletter_category_id: z.number(),
|
|
245
|
+
category: NewsletterCategorySchema.nullish(),
|
|
246
|
+
name: z.string(),
|
|
247
|
+
subject: z.string(),
|
|
248
|
+
preheader: z.string().nullable().optional(),
|
|
249
|
+
schema_snapshot: NewsletterTemplateSchemaSchema,
|
|
250
|
+
variables: z.record(z.string()).nullable().optional(),
|
|
251
|
+
audience_definition: NewsletterAudienceSchema,
|
|
252
|
+
status: z.nativeEnum(NewsletterCampaignStatus),
|
|
253
|
+
scheduled_at: z.string().nullable().optional(),
|
|
254
|
+
started_at: z.string().nullable().optional(),
|
|
255
|
+
finished_at: z.string().nullable().optional(),
|
|
256
|
+
cancelled_at: z.string().nullable().optional(),
|
|
257
|
+
counts: z.object({
|
|
258
|
+
queued: z.number().nullable(),
|
|
259
|
+
sent: z.number(),
|
|
260
|
+
failed: z.number(),
|
|
261
|
+
bounced: z.number(),
|
|
262
|
+
complained: z.number(),
|
|
263
|
+
}),
|
|
264
|
+
sent_by_user_id: z.number().nullable().optional(),
|
|
265
|
+
sent_by: UserBasicSchema.optional(),
|
|
266
|
+
can: z
|
|
267
|
+
.object({
|
|
268
|
+
update: z.boolean(),
|
|
269
|
+
schedule: z.boolean(),
|
|
270
|
+
send: z.boolean(),
|
|
271
|
+
cancel: z.boolean(),
|
|
272
|
+
test_send: z.boolean(),
|
|
273
|
+
delete: z.boolean(),
|
|
274
|
+
})
|
|
275
|
+
.partial()
|
|
276
|
+
.optional(),
|
|
277
|
+
created_at: z.string().optional(),
|
|
278
|
+
updated_at: z.string().optional(),
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
export type NewsletterCampaign = z.infer<typeof NewsletterCampaignSchema>;
|
|
282
|
+
|
|
283
|
+
export const CreateNewsletterCampaignInputSchema = z.object({
|
|
284
|
+
newsletter_template_id: z.number().nullable().optional(),
|
|
285
|
+
newsletter_category_id: z.number().optional(),
|
|
286
|
+
name: z.string().min(1),
|
|
287
|
+
subject: z.string().optional(),
|
|
288
|
+
preheader: z.string().nullable().optional(),
|
|
289
|
+
schema_snapshot: NewsletterTemplateSchemaSchema.optional(),
|
|
290
|
+
variables: z.record(z.string()).optional(),
|
|
291
|
+
audience_definition: NewsletterAudienceSchema,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
export type CreateNewsletterCampaignInput = z.infer<typeof CreateNewsletterCampaignInputSchema>;
|
|
295
|
+
export type UpdateNewsletterCampaignInput = Partial<CreateNewsletterCampaignInput>;
|
|
296
|
+
|
|
297
|
+
/*
|
|
298
|
+
|------------------------------------------------------------------------
|
|
299
|
+
| Recipient
|
|
300
|
+
|------------------------------------------------------------------------
|
|
301
|
+
*/
|
|
302
|
+
|
|
303
|
+
export enum NewsletterRecipientStatus {
|
|
304
|
+
Queued = "queued",
|
|
305
|
+
Sent = "sent",
|
|
306
|
+
Failed = "failed",
|
|
307
|
+
Bounced = "bounced",
|
|
308
|
+
Complained = "complained",
|
|
309
|
+
Suppressed = "suppressed",
|
|
310
|
+
OptedOut = "opted_out",
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export const NewsletterCampaignRecipientSchema = z.object({
|
|
314
|
+
type: z.literal("newsletter_campaign_recipient").optional(),
|
|
315
|
+
id: z.number(),
|
|
316
|
+
newsletter_campaign_id: z.number(),
|
|
317
|
+
user_id: z.number().nullable(),
|
|
318
|
+
user: UserBasicSchema.optional(),
|
|
319
|
+
email: z.string(),
|
|
320
|
+
status: z.nativeEnum(NewsletterRecipientStatus),
|
|
321
|
+
error: z.string().nullable().optional(),
|
|
322
|
+
message_id: z.string().nullable().optional(),
|
|
323
|
+
queued_at: z.string().nullable().optional(),
|
|
324
|
+
sent_at: z.string().nullable().optional(),
|
|
325
|
+
bounced_at: z.string().nullable().optional(),
|
|
326
|
+
complained_at: z.string().nullable().optional(),
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
export type NewsletterCampaignRecipient = z.infer<typeof NewsletterCampaignRecipientSchema>;
|
|
330
|
+
|
|
331
|
+
/*
|
|
332
|
+
|------------------------------------------------------------------------
|
|
333
|
+
| Preference (per-user per-category)
|
|
334
|
+
|------------------------------------------------------------------------
|
|
335
|
+
*/
|
|
336
|
+
|
|
337
|
+
export const NewsletterPreferenceRowSchema = z.object({
|
|
338
|
+
category_id: z.number(),
|
|
339
|
+
category_key: z.string(),
|
|
340
|
+
name: z.string(),
|
|
341
|
+
description: z.string().nullable().optional(),
|
|
342
|
+
is_mandatory: z.boolean(),
|
|
343
|
+
opted_in: z.boolean(),
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
export type NewsletterPreferenceRow = z.infer<typeof NewsletterPreferenceRowSchema>;
|
|
347
|
+
|
|
348
|
+
/*
|
|
349
|
+
|------------------------------------------------------------------------
|
|
350
|
+
| Variable allowlist (must mirror backend NewsletterBlocksValidator)
|
|
351
|
+
|------------------------------------------------------------------------
|
|
352
|
+
*/
|
|
353
|
+
|
|
354
|
+
export const ALLOWED_NEWSLETTER_VARIABLES = [
|
|
355
|
+
"user.name",
|
|
356
|
+
"user.first_name",
|
|
357
|
+
"user.email",
|
|
358
|
+
"company.name",
|
|
359
|
+
"unsubscribe_url",
|
|
360
|
+
] as const;
|
|
361
|
+
|
|
362
|
+
export type AllowedNewsletterVariable = (typeof ALLOWED_NEWSLETTER_VARIABLES)[number];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const TodoActionSchema = z.object({
|
|
4
|
+
type: z.enum(["open_dialog", "navigate"]),
|
|
5
|
+
target: z.string(),
|
|
6
|
+
params: z.record(z.any()).optional(),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export const TodoKey = z.enum([
|
|
10
|
+
"fill_form",
|
|
11
|
+
"sign_form",
|
|
12
|
+
"review_form",
|
|
13
|
+
"log_booking",
|
|
14
|
+
"add_address",
|
|
15
|
+
"add_required_documents",
|
|
16
|
+
"fill_training_record",
|
|
17
|
+
"sign_training_record",
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
export const TodoSchema = z.object({
|
|
21
|
+
key: TodoKey,
|
|
22
|
+
ref_type: z.string(),
|
|
23
|
+
ref_id: z.number(),
|
|
24
|
+
title: z.string(),
|
|
25
|
+
subtitle: z.string().nullable().optional(),
|
|
26
|
+
due_date: z.string().nullable().optional(),
|
|
27
|
+
is_overdue: z.boolean().default(false),
|
|
28
|
+
priority: z.enum(["low", "normal", "high"]).default("normal"),
|
|
29
|
+
company_id: z.number().nullable().optional(),
|
|
30
|
+
action: TodoActionSchema,
|
|
31
|
+
meta: z.record(z.any()).optional(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export type Todo = z.infer<typeof TodoSchema>;
|
|
35
|
+
export type TodoAction = z.infer<typeof TodoActionSchema>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Shape of one entry in `meta.accepted_alternatives` on
|
|
39
|
+
* `add_required_documents` todos — the other templates of the requirement's
|
|
40
|
+
* OR bucket that would satisfy it equally (backend:
|
|
41
|
+
* MissingDocumentTodoSource::selectOrAlternative()).
|
|
42
|
+
*/
|
|
43
|
+
export interface TodoAcceptedAlternative {
|
|
44
|
+
form_template_id: number;
|
|
45
|
+
form_template_name: string | null;
|
|
46
|
+
}
|