@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
@@ -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>;
@@ -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(z.any()).optional(),
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
+ }