@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
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// Minimal GeoJSON types (we don't depend on @types/geojson directly).
|
|
4
|
+
|
|
5
|
+
export type GeoJsonGeometry =
|
|
6
|
+
| { type: "Point"; coordinates: number[] }
|
|
7
|
+
| { type: "MultiPoint"; coordinates: number[][] }
|
|
8
|
+
| { type: "LineString"; coordinates: number[][] }
|
|
9
|
+
| { type: "MultiLineString"; coordinates: number[][][] }
|
|
10
|
+
| { type: "Polygon"; coordinates: number[][][] }
|
|
11
|
+
| { type: "MultiPolygon"; coordinates: number[][][][] };
|
|
12
|
+
|
|
13
|
+
export interface GeoJsonFeature<P = Record<string, unknown>> {
|
|
14
|
+
type: "Feature";
|
|
15
|
+
id?: number | string;
|
|
16
|
+
geometry: GeoJsonGeometry;
|
|
17
|
+
properties: P | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface GeoJsonFeatureCollection<P = Record<string, unknown>> {
|
|
21
|
+
type: "FeatureCollection";
|
|
22
|
+
features: GeoJsonFeature<P>[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ── Enums (mirror backend) ──
|
|
26
|
+
|
|
27
|
+
export enum LocationMapKind {
|
|
28
|
+
Facility = "facility",
|
|
29
|
+
TrainingArea = "training_area",
|
|
30
|
+
Parking = "parking",
|
|
31
|
+
Procedural = "procedural",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export enum LocationFeatureVisibility {
|
|
35
|
+
Public = "public",
|
|
36
|
+
Members = "members",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const ALLOWED_GEOMETRY_TYPES = [
|
|
40
|
+
"Point",
|
|
41
|
+
"MultiPoint",
|
|
42
|
+
"LineString",
|
|
43
|
+
"MultiLineString",
|
|
44
|
+
"Polygon",
|
|
45
|
+
"MultiPolygon",
|
|
46
|
+
] as const;
|
|
47
|
+
|
|
48
|
+
export type AllowedGeometryType = (typeof ALLOWED_GEOMETRY_TYPES)[number];
|
|
49
|
+
|
|
50
|
+
// ── Viewport ──
|
|
51
|
+
|
|
52
|
+
export const ViewportSchema = z.object({
|
|
53
|
+
center_lng: z.number().min(-180).max(180),
|
|
54
|
+
center_lat: z.number().min(-90).max(90),
|
|
55
|
+
zoom: z.number().min(0).max(24),
|
|
56
|
+
pitch: z.number().min(0).max(85).optional(),
|
|
57
|
+
bearing: z.number().min(-360).max(360).optional(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export type Viewport = z.infer<typeof ViewportSchema>;
|
|
61
|
+
|
|
62
|
+
// ── Geometry (loose GeoJSON-compatible) ──
|
|
63
|
+
|
|
64
|
+
export const GeometrySchema = z.object({
|
|
65
|
+
type: z.enum(ALLOWED_GEOMETRY_TYPES),
|
|
66
|
+
coordinates: z.array(z.unknown()),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// ── LocationMap ──
|
|
70
|
+
|
|
71
|
+
export interface LocationMap {
|
|
72
|
+
type: "location_map";
|
|
73
|
+
id: number;
|
|
74
|
+
company_id: number;
|
|
75
|
+
name: string;
|
|
76
|
+
slug: string;
|
|
77
|
+
kind: string;
|
|
78
|
+
description: string | null;
|
|
79
|
+
default_viewport: Viewport | null;
|
|
80
|
+
is_active: boolean;
|
|
81
|
+
created_by: number | null;
|
|
82
|
+
creator?: { id: number; firstname?: string; lastname?: string } | null;
|
|
83
|
+
features?: LocationMapFeature[];
|
|
84
|
+
created_at: string;
|
|
85
|
+
updated_at: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const CreateLocationMapSchema = z.object({
|
|
89
|
+
name: z.string().min(1).max(255),
|
|
90
|
+
slug: z.string().min(1).max(255).optional(),
|
|
91
|
+
kind: z.string().max(64).optional(),
|
|
92
|
+
description: z.string().nullable().optional(),
|
|
93
|
+
default_viewport: ViewportSchema.nullable().optional(),
|
|
94
|
+
is_active: z.boolean().optional(),
|
|
95
|
+
});
|
|
96
|
+
export type CreateLocationMapInput = z.infer<typeof CreateLocationMapSchema>;
|
|
97
|
+
|
|
98
|
+
export const UpdateLocationMapSchema = CreateLocationMapSchema.partial();
|
|
99
|
+
export type UpdateLocationMapInput = z.infer<typeof UpdateLocationMapSchema>;
|
|
100
|
+
|
|
101
|
+
// ── LocationMapFeature ──
|
|
102
|
+
|
|
103
|
+
export interface LocationMapFeature {
|
|
104
|
+
type: "location_map_feature";
|
|
105
|
+
id: number;
|
|
106
|
+
location_map_id: number;
|
|
107
|
+
name: string | null;
|
|
108
|
+
kind: string;
|
|
109
|
+
visibility: LocationFeatureVisibility | string;
|
|
110
|
+
geometry: GeoJsonGeometry;
|
|
111
|
+
geometry_type: AllowedGeometryType | string;
|
|
112
|
+
properties: Record<string, unknown>;
|
|
113
|
+
display_order: number;
|
|
114
|
+
created_at: string;
|
|
115
|
+
updated_at: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const CreateLocationMapFeatureSchema = z.object({
|
|
119
|
+
name: z.string().max(255).nullable().optional(),
|
|
120
|
+
kind: z.string().min(1).max(64),
|
|
121
|
+
visibility: z.nativeEnum(LocationFeatureVisibility).optional(),
|
|
122
|
+
geometry: GeometrySchema,
|
|
123
|
+
properties: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
124
|
+
display_order: z.number().int().min(0).optional(),
|
|
125
|
+
});
|
|
126
|
+
export type CreateLocationMapFeatureInput = z.infer<
|
|
127
|
+
typeof CreateLocationMapFeatureSchema
|
|
128
|
+
>;
|
|
129
|
+
|
|
130
|
+
export const UpdateLocationMapFeatureSchema =
|
|
131
|
+
CreateLocationMapFeatureSchema.partial().extend({
|
|
132
|
+
geometry: GeometrySchema.optional(),
|
|
133
|
+
});
|
|
134
|
+
export type UpdateLocationMapFeatureInput = z.infer<
|
|
135
|
+
typeof UpdateLocationMapFeatureSchema
|
|
136
|
+
>;
|
|
137
|
+
|
|
138
|
+
// ── Bulk import ──
|
|
139
|
+
|
|
140
|
+
export const ImportFeaturesSchema = z.object({
|
|
141
|
+
type: z.literal("FeatureCollection"),
|
|
142
|
+
features: z
|
|
143
|
+
.array(
|
|
144
|
+
z.object({
|
|
145
|
+
type: z.literal("Feature"),
|
|
146
|
+
geometry: GeometrySchema,
|
|
147
|
+
properties: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
148
|
+
}),
|
|
149
|
+
)
|
|
150
|
+
.min(1)
|
|
151
|
+
.max(500),
|
|
152
|
+
defaults: z
|
|
153
|
+
.object({
|
|
154
|
+
kind: z.string().max(64).optional(),
|
|
155
|
+
visibility: z.nativeEnum(LocationFeatureVisibility).optional(),
|
|
156
|
+
})
|
|
157
|
+
.optional(),
|
|
158
|
+
});
|
|
159
|
+
export type ImportFeaturesInput = z.infer<typeof ImportFeaturesSchema>;
|
|
160
|
+
|
|
161
|
+
// ── Viewer (GeoJSON FeatureCollection returned by API) ──
|
|
162
|
+
|
|
163
|
+
export interface LocationMapGeoJsonProperties {
|
|
164
|
+
id: number;
|
|
165
|
+
name: string | null;
|
|
166
|
+
kind: string;
|
|
167
|
+
visibility: string;
|
|
168
|
+
display_order: number;
|
|
169
|
+
[key: string]: unknown;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export type LocationMapGeoJson =
|
|
173
|
+
GeoJsonFeatureCollection<LocationMapGeoJsonProperties>;
|
|
174
|
+
export type LocationMapFeatureGeoJson =
|
|
175
|
+
GeoJsonFeature<LocationMapGeoJsonProperties>;
|
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(),
|
|
@@ -348,7 +375,7 @@ export const extendedAirplaneSchema = airplaneSchema.extend({
|
|
|
348
375
|
VFRNight: z.boolean().optional(),
|
|
349
376
|
IFR: z.boolean().optional(),
|
|
350
377
|
IFRKnownIcing: z.boolean().optional(),
|
|
351
|
-
equipment: z.array(
|
|
378
|
+
equipment: z.array(equipmentSchema).optional(),
|
|
352
379
|
contact: contactAttributeSchema.optional(),
|
|
353
380
|
hints: z
|
|
354
381
|
.object({
|
|
@@ -843,15 +870,6 @@ export const tagSchema = z.object({
|
|
|
843
870
|
color: z.string().nullable().optional(),
|
|
844
871
|
});
|
|
845
872
|
|
|
846
|
-
// Slim attached progress-check sheet (course_check_syllabi pivot),
|
|
847
|
-
// serialized on course show/update payloads.
|
|
848
|
-
export const courseCheckSyllabusSchema = z.object({
|
|
849
|
-
id: z.number(),
|
|
850
|
-
name: z.string(),
|
|
851
|
-
version: z.number(),
|
|
852
|
-
kind: z.enum(["regular", "progress_check"]).default("regular"),
|
|
853
|
-
});
|
|
854
|
-
|
|
855
873
|
export const courseSchema = z.object({
|
|
856
874
|
type: z.literal("course"),
|
|
857
875
|
id: z.number(),
|
|
@@ -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
|
+
}
|