@blackcode_sa/metaestetics-api 1.4.18 → 1.5.1

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.
@@ -5,6 +5,7 @@ import {
5
5
  } from "../../backoffice/types/static/certification.types";
6
6
 
7
7
  export const PRACTITIONERS_COLLECTION = "practitioners";
8
+ export const REGISTER_TOKENS_COLLECTION = "register_tokens";
8
9
 
9
10
  /**
10
11
  * Osnovne informacije o zdravstvenom radniku
@@ -35,6 +36,43 @@ export interface PractitionerCertification {
35
36
  verificationStatus: "pending" | "verified" | "rejected";
36
37
  }
37
38
 
39
+ /**
40
+ * Interfejs za radno vreme zdravstvenog radnika u klinici
41
+ */
42
+ export interface PractitionerClinicWorkingHours {
43
+ clinicId: string;
44
+ workingHours: {
45
+ monday: { start: string; end: string } | null;
46
+ tuesday: { start: string; end: string } | null;
47
+ wednesday: { start: string; end: string } | null;
48
+ thursday: { start: string; end: string } | null;
49
+ friday: { start: string; end: string } | null;
50
+ saturday: { start: string; end: string } | null;
51
+ sunday: { start: string; end: string } | null;
52
+ };
53
+ isActive: boolean;
54
+ createdAt: Timestamp;
55
+ updatedAt: Timestamp;
56
+ }
57
+
58
+ /**
59
+ * Status of practitioner profile
60
+ */
61
+ export enum PractitionerStatus {
62
+ DRAFT = "draft",
63
+ ACTIVE = "active",
64
+ }
65
+
66
+ /**
67
+ * Token status for practitioner invitations
68
+ */
69
+ export enum PractitionerTokenStatus {
70
+ ACTIVE = "active",
71
+ USED = "used",
72
+ EXPIRED = "expired",
73
+ REVOKED = "revoked",
74
+ }
75
+
38
76
  /**
39
77
  * Interfejs za zdravstvenog radnika
40
78
  */
@@ -44,8 +82,10 @@ export interface Practitioner {
44
82
  basicInfo: PractitionerBasicInfo;
45
83
  certification: PractitionerCertification;
46
84
  clinics: string[]; // Reference na klinike gde radi
85
+ clinicWorkingHours: PractitionerClinicWorkingHours[]; // Radno vreme za svaku kliniku
47
86
  isActive: boolean;
48
87
  isVerified: boolean;
88
+ status: PractitionerStatus;
49
89
  createdAt: Timestamp;
50
90
  updatedAt: Timestamp;
51
91
  }
@@ -58,8 +98,22 @@ export interface CreatePractitionerData {
58
98
  basicInfo: PractitionerBasicInfo;
59
99
  certification: PractitionerCertification;
60
100
  clinics?: string[];
101
+ clinicWorkingHours?: PractitionerClinicWorkingHours[];
61
102
  isActive: boolean;
62
103
  isVerified: boolean;
104
+ status?: PractitionerStatus;
105
+ }
106
+
107
+ /**
108
+ * Tip za kreiranje draft profila zdravstvenog radnika
109
+ */
110
+ export interface CreateDraftPractitionerData {
111
+ basicInfo: PractitionerBasicInfo;
112
+ certification: PractitionerCertification;
113
+ clinics?: string[];
114
+ clinicWorkingHours?: PractitionerClinicWorkingHours[];
115
+ isActive?: boolean;
116
+ isVerified?: boolean;
63
117
  }
64
118
 
65
119
  /**
@@ -113,3 +167,30 @@ export interface PractitionerWorkingHours {
113
167
  createdAt: Timestamp;
114
168
  updatedAt: Timestamp;
115
169
  }
170
+
171
+ /**
172
+ * Token za pozivanje zdravstvenog radnika
173
+ */
174
+ export interface PractitionerToken {
175
+ id: string;
176
+ token: string;
177
+ practitionerId: string;
178
+ email: string;
179
+ clinicId: string;
180
+ status: PractitionerTokenStatus;
181
+ createdBy: string;
182
+ createdAt: Timestamp;
183
+ expiresAt: Timestamp;
184
+ usedBy?: string;
185
+ usedAt?: Timestamp;
186
+ }
187
+
188
+ /**
189
+ * Tip za kreiranje tokena za zdravstvenog radnika
190
+ */
191
+ export interface CreatePractitionerTokenData {
192
+ practitionerId: string;
193
+ email: string;
194
+ clinicId: string;
195
+ expiresAt?: Date;
196
+ }
@@ -0,0 +1,39 @@
1
+ import { Timestamp } from "firebase-admin/firestore";
2
+ import { ClinicLocation, ClinicContactInfo } from "../clinic";
3
+ import { PractitionerCertification } from "../practitioner";
4
+ import { Gender } from "../patient";
5
+ /**
6
+ * Interface for clinic profile information
7
+ */
8
+ export interface ClinicInfo {
9
+ id: string;
10
+ featuredPhoto: string;
11
+ name: string;
12
+ description: string;
13
+ location: ClinicLocation;
14
+ contactInfo: ClinicContactInfo;
15
+ }
16
+
17
+ /**
18
+ * Interface for practitioner profile information
19
+ */
20
+ export interface PractitionerProfileInfo {
21
+ id: string;
22
+ practitionerPhoto: string | null;
23
+ name: string;
24
+ email: string;
25
+ phone: string | null;
26
+ certification: PractitionerCertification;
27
+ }
28
+
29
+ /**
30
+ * Interface for patient profile information
31
+ */
32
+ export interface PatientProfileInfo {
33
+ id: string;
34
+ fullName: string;
35
+ email: string;
36
+ phone: string | null;
37
+ dateOfBirth: Timestamp;
38
+ gender: Gender;
39
+ }
@@ -0,0 +1,223 @@
1
+ import { z } from "zod";
2
+ import { Timestamp, FieldValue } from "firebase/firestore";
3
+ import {
4
+ CalendarEventStatus,
5
+ CalendarEventType,
6
+ CalendarSyncStatus,
7
+ } from "../types/calendar";
8
+ import { SyncedCalendarProvider } from "../types/calendar/synced-calendar.types";
9
+ import {
10
+ Currency,
11
+ PricingMeasure,
12
+ } from "../backoffice/types/static/pricing.types";
13
+ import { clinicLocationSchema } from "./clinic.schema";
14
+ import {
15
+ clinicInfoSchema,
16
+ patientProfileInfoSchema,
17
+ practitionerProfileInfoSchema,
18
+ } from "./profile-info.schema";
19
+
20
+ /**
21
+ * Minimum appointment duration in minutes
22
+ */
23
+ const MIN_APPOINTMENT_DURATION = 15;
24
+
25
+ /**
26
+ * Validation schema for calendar event time
27
+ */
28
+ export const calendarEventTimeSchema = z
29
+ .object({
30
+ start: z.instanceof(Date).or(z.instanceof(Timestamp)),
31
+ end: z.instanceof(Date).or(z.instanceof(Timestamp)),
32
+ })
33
+ .refine(
34
+ (data) => {
35
+ // Convert to Date objects for comparison if they are Timestamps
36
+ const startDate =
37
+ data.start instanceof Timestamp ? data.start.toDate() : data.start;
38
+ const endDate =
39
+ data.end instanceof Timestamp ? data.end.toDate() : data.end;
40
+
41
+ // Ensure end time is after start time
42
+ return startDate < endDate;
43
+ },
44
+ {
45
+ message: "End time must be after start time",
46
+ path: ["end"],
47
+ }
48
+ )
49
+ .refine(
50
+ (data) => {
51
+ // Convert to Date objects for comparison if they are Timestamps
52
+ const startDate =
53
+ data.start instanceof Timestamp ? data.start.toDate() : data.start;
54
+
55
+ // Ensure start time is in the future
56
+ return startDate > new Date();
57
+ },
58
+ {
59
+ message: "Appointment must be scheduled in the future",
60
+ path: ["start"],
61
+ }
62
+ );
63
+
64
+ /**
65
+ * Validation schema for time slot
66
+ */
67
+ export const timeSlotSchema = z
68
+ .object({
69
+ start: z.date(),
70
+ end: z.date(),
71
+ isAvailable: z.boolean(),
72
+ })
73
+ .refine((data) => data.start < data.end, {
74
+ message: "End time must be after start time",
75
+ path: ["end"],
76
+ });
77
+
78
+ /**
79
+ * Validation schema for synced calendar event
80
+ */
81
+ export const syncedCalendarEventSchema = z.object({
82
+ eventId: z.string(),
83
+ syncedCalendarProvider: z.nativeEnum(SyncedCalendarProvider),
84
+ syncedAt: z.instanceof(Date).or(z.instanceof(Timestamp)),
85
+ });
86
+
87
+ /**
88
+ * Validation schema for procedure info
89
+ */
90
+ export const procedureInfoSchema = z.object({
91
+ name: z.string(),
92
+ description: z.string(),
93
+ duration: z.number().min(MIN_APPOINTMENT_DURATION),
94
+ price: z.number().min(0),
95
+ currency: z.nativeEnum(Currency),
96
+ });
97
+
98
+ /**
99
+ * Validation schema for procedure categorization
100
+ */
101
+ export const procedureCategorizationSchema = z.object({
102
+ procedureFamily: z.string(), // Replace with proper enum when available
103
+ procedureCategory: z.string(), // Replace with proper enum when available
104
+ procedureSubcategory: z.string(), // Replace with proper enum when available
105
+ procedureTechnology: z.string(), // Replace with proper enum when available
106
+ procedureProduct: z.string(), // Replace with proper enum when available
107
+ });
108
+
109
+ /**
110
+ * Validation schema for creating an appointment
111
+ */
112
+ export const createAppointmentSchema = z
113
+ .object({
114
+ clinicId: z.string().min(1, "Clinic ID is required"),
115
+ doctorId: z.string().min(1, "Doctor ID is required"),
116
+ patientId: z.string().min(1, "Patient ID is required"),
117
+ procedureId: z.string().min(1, "Procedure ID is required"),
118
+ eventLocation: clinicLocationSchema,
119
+ eventTime: calendarEventTimeSchema,
120
+ description: z.string().optional(),
121
+ })
122
+ .refine(
123
+ (data) => {
124
+ // If we have access to the actual data, we could validate:
125
+ // - Doctor works at the clinic
126
+ // - Procedure is available at the clinic
127
+ // - Patient is eligible for the procedure
128
+ // These validations will be handled in the service layer
129
+ return true;
130
+ },
131
+ {
132
+ message: "Invalid appointment parameters",
133
+ }
134
+ );
135
+
136
+ /**
137
+ * Validation schema for updating an appointment
138
+ */
139
+ export const updateAppointmentSchema = z.object({
140
+ appointmentId: z.string().min(1, "Appointment ID is required"),
141
+ clinicId: z.string().min(1, "Clinic ID is required"),
142
+ doctorId: z.string().min(1, "Doctor ID is required"),
143
+ patientId: z.string().min(1, "Patient ID is required"),
144
+ eventTime: calendarEventTimeSchema.optional(),
145
+ description: z.string().optional(),
146
+ status: z.nativeEnum(CalendarEventStatus).optional(),
147
+ });
148
+
149
+ /**
150
+ * Validation schema for creating a calendar event
151
+ */
152
+ export const createCalendarEventSchema = z.object({
153
+ id: z.string(),
154
+ clinicBranchId: z.string().nullable().optional(),
155
+ clinicBranchInfo: z.any().nullable().optional(),
156
+ practitionerProfileId: z.string().nullable().optional(),
157
+ practitionerProfileInfo: practitionerProfileInfoSchema.nullable().optional(),
158
+ patientProfileId: z.string().nullable().optional(),
159
+ patientProfileInfo: patientProfileInfoSchema.nullable().optional(),
160
+ procedureId: z.string().nullable().optional(),
161
+ appointmentId: z.string().nullable().optional(),
162
+ syncedCalendarEventId: z
163
+ .array(syncedCalendarEventSchema)
164
+ .nullable()
165
+ .optional(),
166
+ eventName: z.string().min(1, "Event name is required"),
167
+ eventLocation: clinicLocationSchema.optional(),
168
+ eventTime: calendarEventTimeSchema,
169
+ description: z.string().optional(),
170
+ status: z.nativeEnum(CalendarEventStatus),
171
+ syncStatus: z.nativeEnum(CalendarSyncStatus),
172
+ eventType: z.nativeEnum(CalendarEventType),
173
+ createdAt: z.any(), // FieldValue for server timestamp
174
+ updatedAt: z.any(), // FieldValue for server timestamp
175
+ });
176
+
177
+ /**
178
+ * Validation schema for updating a calendar event
179
+ */
180
+ export const updateCalendarEventSchema = z.object({
181
+ syncedCalendarEventId: z
182
+ .array(syncedCalendarEventSchema)
183
+ .nullable()
184
+ .optional(),
185
+ appointmentId: z.string().nullable().optional(),
186
+ eventName: z.string().optional(),
187
+ eventTime: calendarEventTimeSchema.optional(),
188
+ description: z.string().optional(),
189
+ status: z.nativeEnum(CalendarEventStatus).optional(),
190
+ syncStatus: z.nativeEnum(CalendarSyncStatus).optional(),
191
+ eventType: z.nativeEnum(CalendarEventType).optional(),
192
+ updatedAt: z.any(), // FieldValue for server timestamp
193
+ });
194
+
195
+ /**
196
+ * Validation schema for calendar event
197
+ */
198
+ export const calendarEventSchema = z.object({
199
+ id: z.string(),
200
+ clinicBranchId: z.string().nullable().optional(),
201
+ clinicBranchInfo: z.any().nullable().optional(), // Will be replaced with proper clinic info schema
202
+ practitionerProfileId: z.string().nullable().optional(),
203
+ practitionerProfileInfo: practitionerProfileInfoSchema.nullable().optional(),
204
+ patientProfileId: z.string().nullable().optional(),
205
+ patientProfileInfo: patientProfileInfoSchema.nullable().optional(),
206
+ procedureId: z.string().nullable().optional(),
207
+ procedureInfo: procedureInfoSchema.nullable().optional(),
208
+ procedureCategorization: procedureCategorizationSchema.nullable().optional(),
209
+ appointmentId: z.string().nullable().optional(),
210
+ syncedCalendarEventId: z
211
+ .array(syncedCalendarEventSchema)
212
+ .nullable()
213
+ .optional(),
214
+ eventName: z.string(),
215
+ eventLocation: clinicLocationSchema.optional(),
216
+ eventTime: calendarEventTimeSchema,
217
+ description: z.string().optional(),
218
+ status: z.nativeEnum(CalendarEventStatus),
219
+ syncStatus: z.nativeEnum(CalendarSyncStatus),
220
+ eventType: z.nativeEnum(CalendarEventType),
221
+ createdAt: z.instanceof(Date).or(z.instanceof(Timestamp)),
222
+ updatedAt: z.instanceof(Date).or(z.instanceof(Timestamp)),
223
+ });
@@ -4,6 +4,10 @@ import {
4
4
  CertificationLevel,
5
5
  CertificationSpecialty,
6
6
  } from "../backoffice/types/static/certification.types";
7
+ import {
8
+ PractitionerStatus,
9
+ PractitionerTokenStatus,
10
+ } from "../types/practitioner";
7
11
 
8
12
  /**
9
13
  * Šema za validaciju osnovnih informacija o zdravstvenom radniku
@@ -60,6 +64,25 @@ export const practitionerWorkingHoursSchema = z.object({
60
64
  updatedAt: z.instanceof(Timestamp),
61
65
  });
62
66
 
67
+ /**
68
+ * Šema za validaciju radnog vremena u klinici
69
+ */
70
+ export const practitionerClinicWorkingHoursSchema = z.object({
71
+ clinicId: z.string().min(1),
72
+ workingHours: z.object({
73
+ monday: timeSlotSchema,
74
+ tuesday: timeSlotSchema,
75
+ wednesday: timeSlotSchema,
76
+ thursday: timeSlotSchema,
77
+ friday: timeSlotSchema,
78
+ saturday: timeSlotSchema,
79
+ sunday: timeSlotSchema,
80
+ }),
81
+ isActive: z.boolean(),
82
+ createdAt: z.instanceof(Timestamp),
83
+ updatedAt: z.instanceof(Timestamp),
84
+ });
85
+
63
86
  /**
64
87
  * Šema za validaciju recenzije zdravstvenog radnika
65
88
  */
@@ -96,8 +119,10 @@ export const practitionerSchema = z.object({
96
119
  basicInfo: practitionerBasicInfoSchema,
97
120
  certification: practitionerCertificationSchema,
98
121
  clinics: z.array(z.string()),
122
+ clinicWorkingHours: z.array(practitionerClinicWorkingHoursSchema),
99
123
  isActive: z.boolean(),
100
124
  isVerified: z.boolean(),
125
+ status: z.nativeEnum(PractitionerStatus),
101
126
  createdAt: z.instanceof(Timestamp),
102
127
  updatedAt: z.instanceof(Timestamp),
103
128
  });
@@ -110,6 +135,47 @@ export const createPractitionerSchema = z.object({
110
135
  basicInfo: practitionerBasicInfoSchema,
111
136
  certification: practitionerCertificationSchema,
112
137
  clinics: z.array(z.string()).optional(),
138
+ clinicWorkingHours: z.array(practitionerClinicWorkingHoursSchema).optional(),
113
139
  isActive: z.boolean(),
114
140
  isVerified: z.boolean(),
141
+ status: z.nativeEnum(PractitionerStatus).optional(),
142
+ });
143
+
144
+ /**
145
+ * Šema za validaciju podataka pri kreiranju draft profila zdravstvenog radnika
146
+ */
147
+ export const createDraftPractitionerSchema = z.object({
148
+ basicInfo: practitionerBasicInfoSchema,
149
+ certification: practitionerCertificationSchema,
150
+ clinics: z.array(z.string()).optional(),
151
+ clinicWorkingHours: z.array(practitionerClinicWorkingHoursSchema).optional(),
152
+ isActive: z.boolean().optional().default(false),
153
+ isVerified: z.boolean().optional().default(false),
154
+ });
155
+
156
+ /**
157
+ * Šema za validaciju tokena za zdravstvenog radnika
158
+ */
159
+ export const practitionerTokenSchema = z.object({
160
+ id: z.string().min(1),
161
+ token: z.string().min(6),
162
+ practitionerId: z.string().min(1),
163
+ email: z.string().email(),
164
+ clinicId: z.string().min(1),
165
+ status: z.nativeEnum(PractitionerTokenStatus),
166
+ createdBy: z.string().min(1),
167
+ createdAt: z.instanceof(Timestamp),
168
+ expiresAt: z.instanceof(Timestamp),
169
+ usedBy: z.string().optional(),
170
+ usedAt: z.instanceof(Timestamp).optional(),
171
+ });
172
+
173
+ /**
174
+ * Šema za validaciju podataka pri kreiranju tokena za zdravstvenog radnika
175
+ */
176
+ export const createPractitionerTokenSchema = z.object({
177
+ practitionerId: z.string().min(1),
178
+ email: z.string().email(),
179
+ clinicId: z.string().min(1),
180
+ expiresAt: z.date().optional(),
115
181
  });
@@ -0,0 +1,41 @@
1
+ import { z } from "zod";
2
+ import { Timestamp } from "firebase/firestore";
3
+ import { clinicLocationSchema, clinicContactInfoSchema } from "./clinic.schema";
4
+ import { practitionerCertificationSchema } from "./practitioner.schema";
5
+ import { Gender } from "../types/patient";
6
+
7
+ /**
8
+ * Validation schema for clinic info
9
+ */
10
+ export const clinicInfoSchema = z.object({
11
+ id: z.string(),
12
+ featuredPhoto: z.string(),
13
+ name: z.string(),
14
+ description: z.string(),
15
+ location: clinicLocationSchema,
16
+ contactInfo: clinicContactInfoSchema,
17
+ });
18
+
19
+ /**
20
+ * Validation schema for practitioner profile info
21
+ */
22
+ export const practitionerProfileInfoSchema = z.object({
23
+ id: z.string(),
24
+ practitionerPhoto: z.string().nullable(),
25
+ name: z.string(),
26
+ email: z.string().email(),
27
+ phone: z.string().nullable(),
28
+ certification: practitionerCertificationSchema,
29
+ });
30
+
31
+ /**
32
+ * Validation schema for patient profile info
33
+ */
34
+ export const patientProfileInfoSchema = z.object({
35
+ id: z.string(),
36
+ fullName: z.string(),
37
+ email: z.string().email(),
38
+ phone: z.string().nullable(),
39
+ dateOfBirth: z.instanceof(Timestamp),
40
+ gender: z.nativeEnum(Gender),
41
+ });