@brite-future-schools-contracts/contracts 1.0.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/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./schemas";
2
+ export * from "./contracts";
@@ -0,0 +1,476 @@
1
+ import { z } from "zod";
2
+
3
+ // ─── Primitives ──────────────────────────────────────────────────────────────
4
+
5
+ export const uuidSchema = z.string().uuid();
6
+
7
+ export const paginationSchema = z.object({
8
+ page: z.number().int().min(1).default(1),
9
+ limit: z.number().int().min(1).max(100).default(20),
10
+ });
11
+
12
+ export const dateRangeSchema = z.object({
13
+ from: z.string().datetime().optional(),
14
+ to: z.string().datetime().optional(),
15
+ });
16
+
17
+ // ─── Enums ───────────────────────────────────────────────────────────────────
18
+
19
+ export const genderEnum = z.enum(["male", "female"]);
20
+
21
+ export const userStatusEnum = z.enum(["active", "inactive", "suspended"]);
22
+
23
+ export const systemRoleEnum = z.enum([
24
+ "DIRECTOR",
25
+ "PRINCIPAL",
26
+ "DEPUTY_PRINCIPAL",
27
+ "HOD",
28
+ "CLASS_TEACHER",
29
+ "SUBJECT_TEACHER",
30
+ "BURSAR",
31
+ "HR_ADMIN",
32
+ "SCHOOL_NURSE",
33
+ "LIBRARIAN",
34
+ "LAB_TECHNICIAN",
35
+ "SPORTS_COACH",
36
+ "COUNSELLOR",
37
+ "CARETAKER",
38
+ "SECURITY",
39
+ "KITCHEN_STAFF",
40
+ "DRIVER",
41
+ "IT_ADMIN",
42
+ "STUDENT",
43
+ "PARENT",
44
+ "ORG_SPONSOR",
45
+ "INDIVIDUAL_SPONSOR",
46
+ ]);
47
+
48
+ export type SystemRole = z.infer<typeof systemRoleEnum>;
49
+
50
+ // ─── Auth ─────────────────────────────────────────────────────────────────────
51
+
52
+ export const loginInputSchema = z.object({
53
+ email: z.string().email(),
54
+ password: z.string().min(8),
55
+ deviceFingerprint: z.string().optional(),
56
+ });
57
+
58
+ export const loginOutputSchema = z.object({
59
+ accessToken: z.string(),
60
+ refreshToken: z.string().optional(),
61
+ user: z.object({
62
+ id: uuidSchema,
63
+ firstName: z.string(),
64
+ middleName: z.string().nullable(),
65
+ lastName: z.string(),
66
+ email: z.string().email(),
67
+ avatarUrl: z.string().nullable(),
68
+ roles: z.array(
69
+ z.object({
70
+ id: uuidSchema,
71
+ name: z.string(),
72
+ branchId: uuidSchema.nullable(),
73
+ }),
74
+ ),
75
+ activeBranchId: uuidSchema.nullable(),
76
+ isMfaEnabled: z.boolean(),
77
+ }),
78
+ });
79
+
80
+ export const refreshTokenOutputSchema = z.object({
81
+ accessToken: z.string(),
82
+ });
83
+
84
+ export const refreshInputSchema = z.object({
85
+ refreshToken: z.string().optional(),
86
+ });
87
+
88
+ export const mfaVerifyInputSchema = z.object({
89
+ totp: z.string().length(6),
90
+ });
91
+
92
+ export const changePasswordInputSchema = z.object({
93
+ currentPassword: z.string().min(8),
94
+ newPassword: z.string().min(8),
95
+ });
96
+
97
+ // ─── User / Profile ───────────────────────────────────────────────────────────
98
+
99
+ export const userSchema = z.object({
100
+ id: uuidSchema,
101
+ firstName: z.string(),
102
+ middleName: z.string().nullable(),
103
+ lastName: z.string(),
104
+ email: z.string().email(),
105
+ phone: z.string().nullable(),
106
+ avatarUrl: z.string().nullable(),
107
+ isActive: z.boolean(),
108
+ isMfaEnabled: z.boolean(),
109
+ lastLoginAt: z.string().datetime().nullable(),
110
+ createdAt: z.string().datetime(),
111
+ });
112
+
113
+ // ─── Branches & Organisation ─────────────────────────────────────────────────
114
+
115
+ export const branchSchema = z.object({
116
+ id: uuidSchema,
117
+ name: z.string(),
118
+ code: z.string(),
119
+ address: z.string(),
120
+ county: z.string(),
121
+ phone: z.string().nullable(),
122
+ email: z.string().email().nullable(),
123
+ logoUrl: z.string().nullable(),
124
+ motto: z.string().nullable(),
125
+ principalId: uuidSchema.nullable(),
126
+ mpesaPaybill: z.string().nullable(),
127
+ featureFlags: z.record(z.boolean()),
128
+ createdAt: z.string().datetime(),
129
+ });
130
+
131
+ export const createBranchInputSchema = z.object({
132
+ name: z.string().min(2).max(100),
133
+ code: z.string().min(2).max(20),
134
+ address: z.string(),
135
+ county: z.string(),
136
+ phone: z.string().optional(),
137
+ email: z.string().email().optional(),
138
+ motto: z.string().optional(),
139
+ });
140
+
141
+ // ─── Students ─────────────────────────────────────────────────────────────────
142
+
143
+ export const studentStatusEnum = z.enum([
144
+ "active",
145
+ "transferred",
146
+ "graduated",
147
+ "suspended",
148
+ "withdrawn",
149
+ ]);
150
+
151
+ export const studentSchema = z.object({
152
+ id: uuidSchema,
153
+ branchId: uuidSchema,
154
+ admissionNo: z.string(),
155
+ nemisNo: z.string().nullable(),
156
+ firstName: z.string(),
157
+ middleName: z.string().nullable(),
158
+ lastName: z.string(),
159
+ dateOfBirth: z.string(),
160
+ gender: genderEnum,
161
+ photoUrl: z.string().nullable(),
162
+ bloodGroup: z.string().nullable(),
163
+ admissionDate: z.string(),
164
+ status: studentStatusEnum,
165
+ currentClassId: uuidSchema.nullable(),
166
+ createdAt: z.string().datetime(),
167
+ });
168
+
169
+ export const createStudentInputSchema = z.object({
170
+ admissionNo: z.string().min(1),
171
+ nemisNo: z.string().optional(),
172
+ firstName: z.string().min(1).max(100),
173
+ middleName: z.string().max(100).optional(),
174
+ lastName: z.string().min(1).max(100),
175
+ dateOfBirth: z.string(),
176
+ gender: genderEnum,
177
+ admissionDate: z.string(),
178
+ guardianId: uuidSchema.optional(),
179
+ });
180
+
181
+ export const listStudentsInputSchema = paginationSchema.extend({
182
+ branchId: uuidSchema.optional(),
183
+ classId: uuidSchema.optional(),
184
+ search: z.string().optional(),
185
+ status: studentStatusEnum.optional(),
186
+ termId: uuidSchema.optional(),
187
+ });
188
+
189
+ // ─── Guardian / Sponsor ───────────────────────────────────────────────────────
190
+
191
+ export const guardianSchema = z.object({
192
+ id: uuidSchema,
193
+ userId: uuidSchema,
194
+ firstName: z.string(),
195
+ middleName: z.string().nullable(),
196
+ lastName: z.string(),
197
+ relationship: z.string(),
198
+ phone: z.string(),
199
+ email: z.string().email().nullable(),
200
+ nationalId: z.string().nullable(),
201
+ occupation: z.string().nullable(),
202
+ address: z.string().nullable(),
203
+ });
204
+
205
+ export const sponsorTypeEnum = z.enum(["organisation", "individual"]);
206
+
207
+ export const sponsorSchema = z.object({
208
+ id: uuidSchema,
209
+ branchId: uuidSchema,
210
+ type: sponsorTypeEnum,
211
+ organisationName: z.string().nullable(),
212
+ firstName: z.string().nullable(),
213
+ middleName: z.string().nullable(),
214
+ lastName: z.string().nullable(),
215
+ contactUserId: uuidSchema.nullable(),
216
+ phone: z.string(),
217
+ email: z.string().email().nullable(),
218
+ address: z.string().nullable(),
219
+ });
220
+
221
+ // ─── Academics ────────────────────────────────────────────────────────────────
222
+
223
+ export const gradeLevelEnum = z.enum([
224
+ "PP1",
225
+ "PP2",
226
+ "G1",
227
+ "G2",
228
+ "G3",
229
+ "G4",
230
+ "G5",
231
+ "G6",
232
+ "G7",
233
+ "G8",
234
+ "G9",
235
+ ]);
236
+
237
+ export const assessmentTypeEnum = z.enum([
238
+ "formative",
239
+ "summative",
240
+ "knec_national",
241
+ "informal",
242
+ ]);
243
+
244
+ export const rubricBandEnum = z.enum(["EE", "ME", "AE", "BE"]);
245
+
246
+ export const subjectSchema = z.object({
247
+ id: uuidSchema,
248
+ branchId: uuidSchema,
249
+ name: z.string(),
250
+ code: z.string(),
251
+ gradeLevel: gradeLevelEnum,
252
+ departmentId: uuidSchema.nullable(),
253
+ });
254
+
255
+ export const assessmentScoreInputSchema = z.object({
256
+ assessmentId: uuidSchema,
257
+ studentId: uuidSchema,
258
+ subStrandId: uuidSchema,
259
+ numericScore: z.number().min(0).max(100),
260
+ comments: z.string().optional(),
261
+ });
262
+
263
+ export const reportCardStatusEnum = z.enum([
264
+ "draft",
265
+ "hod_review",
266
+ "principal_review",
267
+ "released",
268
+ "blocked",
269
+ ]);
270
+
271
+ // ─── Finance ──────────────────────────────────────────────────────────────────
272
+
273
+ export const feeItemCategoryEnum = z.enum([
274
+ "compulsory",
275
+ "class_specific",
276
+ "additive",
277
+ ]);
278
+
279
+ export const paymentMethodEnum = z.enum([
280
+ "mpesa",
281
+ "bank",
282
+ "card",
283
+ "cash",
284
+ "cheque",
285
+ ]);
286
+
287
+ export const invoiceStatusEnum = z.enum([
288
+ "draft",
289
+ "sent",
290
+ "partial",
291
+ "paid",
292
+ "overdue",
293
+ "cancelled",
294
+ ]);
295
+
296
+ export const feeItemSchema = z.object({
297
+ id: uuidSchema,
298
+ branchId: uuidSchema,
299
+ name: z.string(),
300
+ category: feeItemCategoryEnum,
301
+ defaultAmount: z.number(),
302
+ applicableGrades: z.array(gradeLevelEnum),
303
+ isActive: z.boolean(),
304
+ });
305
+
306
+ export const recordPaymentInputSchema = z.object({
307
+ studentId: uuidSchema,
308
+ invoiceId: uuidSchema,
309
+ amount: z.number().positive(),
310
+ method: paymentMethodEnum,
311
+ reference: z.string().optional(),
312
+ mpesaReceipt: z.string().optional(),
313
+ paymentDate: z.string(),
314
+ });
315
+
316
+ // ─── HR / Staff ───────────────────────────────────────────────────────────────
317
+
318
+ export const employmentTypeEnum = z.enum([
319
+ "permanent",
320
+ "contract",
321
+ "temporary",
322
+ "intern",
323
+ ]);
324
+
325
+ export const staffSchema = z.object({
326
+ id: uuidSchema,
327
+ branchId: uuidSchema,
328
+ userId: uuidSchema,
329
+ firstName: z.string(),
330
+ middleName: z.string().nullable(),
331
+ lastName: z.string(),
332
+ tscNumber: z.string().nullable(),
333
+ nationalId: z.string(),
334
+ kraPin: z.string().nullable(),
335
+ nhifNo: z.string().nullable(),
336
+ nssfNo: z.string().nullable(),
337
+ phone: z.string(),
338
+ departmentId: uuidSchema.nullable(),
339
+ designation: z.string(),
340
+ employmentType: employmentTypeEnum,
341
+ contractStart: z.string(),
342
+ contractEnd: z.string().nullable(),
343
+ basicSalary: z.number(),
344
+ houseAllowance: z.number().default(0),
345
+ transportAllowance: z.number().default(0),
346
+ bankName: z.string().nullable(),
347
+ bankAccount: z.string().nullable(),
348
+ status: userStatusEnum,
349
+ });
350
+
351
+ export const leaveStatusEnum = z.enum([
352
+ "pending",
353
+ "approved",
354
+ "rejected",
355
+ "cancelled",
356
+ ]);
357
+
358
+ export const leaveRequestInputSchema = z.object({
359
+ leaveTypeId: uuidSchema,
360
+ startDate: z.string(),
361
+ endDate: z.string(),
362
+ reason: z.string().min(10),
363
+ });
364
+
365
+ // ─── Transport ────────────────────────────────────────────────────────────────
366
+
367
+ export const vehicleOwnershipEnum = z.enum(["owned", "contracted"]);
368
+
369
+ export const vehicleTypeEnum = z.enum(["bus", "van", "pickup", "other"]);
370
+
371
+ export const busRegisterStatusEnum = z.enum(["boarded", "alighted", "absent"]);
372
+
373
+ export const tripLogInputSchema = z.object({
374
+ vehicleId: uuidSchema,
375
+ routeId: uuidSchema,
376
+ departureTime: z.string().datetime(),
377
+ tripType: z.enum(["morning", "evening", "errand"]),
378
+ });
379
+
380
+ export const busRegisterEntrySchema = z.object({
381
+ studentId: uuidSchema,
382
+ status: busRegisterStatusEnum,
383
+ });
384
+
385
+ // ─── Assets ───────────────────────────────────────────────────────────────────
386
+
387
+ export const assetCategoryEnum = z.enum([
388
+ "furniture",
389
+ "lab_equipment",
390
+ "ict_device",
391
+ "vehicle",
392
+ "sports_equipment",
393
+ "kitchen_equipment",
394
+ "musical_instrument",
395
+ "classroom_supply",
396
+ "library_book",
397
+ "other",
398
+ ]);
399
+
400
+ export const assetConditionEnum = z.enum([
401
+ "excellent",
402
+ "good",
403
+ "fair",
404
+ "poor",
405
+ "condemned",
406
+ ]);
407
+
408
+ export const maintenanceTicketStatusEnum = z.enum([
409
+ "open",
410
+ "in_progress",
411
+ "resolved",
412
+ "closed",
413
+ ]);
414
+
415
+ // ─── Library ──────────────────────────────────────────────────────────────────
416
+
417
+ export const borrowingStatusEnum = z.enum([
418
+ "active",
419
+ "returned",
420
+ "overdue",
421
+ "lost",
422
+ ]);
423
+
424
+ // ─── Discipline ───────────────────────────────────────────────────────────────
425
+
426
+ export const incidentSeverityEnum = z.enum([
427
+ "minor",
428
+ "moderate",
429
+ "serious",
430
+ "critical",
431
+ ]);
432
+
433
+ export const incidentStatusEnum = z.enum([
434
+ "open",
435
+ "counsellor_review",
436
+ "hod_review",
437
+ "principal_ruling",
438
+ "resolved",
439
+ ]);
440
+
441
+ export const disciplineRulingEnum = z.enum([
442
+ "verbal_warning",
443
+ "written_warning",
444
+ "suspension",
445
+ "expulsion",
446
+ "dismissed",
447
+ ]);
448
+
449
+ // ─── Payroll ──────────────────────────────────────────────────────────────────
450
+
451
+ export const payrollRunStatusEnum = z.enum(["draft", "approved", "paid"]);
452
+
453
+ // ─── Communications ───────────────────────────────────────────────────────────
454
+
455
+ export const notificationTypeEnum = z.enum([
456
+ "report_card",
457
+ "fee_invoice",
458
+ "fee_reminder",
459
+ "fee_block",
460
+ "attendance_absent",
461
+ "bus_departed",
462
+ "bus_arrived",
463
+ "discipline_incident",
464
+ "assignment",
465
+ "notice",
466
+ "leave",
467
+ "payslip",
468
+ ]);
469
+
470
+ export const sendBulkSmsInputSchema = z.object({
471
+ branchId: uuidSchema,
472
+ message: z.string().min(1).max(160),
473
+ recipientType: z.enum(["class", "grade", "whole_school", "custom"]),
474
+ recipientIds: z.array(uuidSchema).optional(),
475
+ scheduleAt: z.string().datetime().optional(),
476
+ });