@blackcode_sa/metaestetics-api 1.4.18 → 1.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.
@@ -0,0 +1,66 @@
1
+ import { Timestamp, FieldValue } from "firebase/firestore";
2
+
3
+ /**
4
+ * Enum for synced calendar provider
5
+ */
6
+ export enum SyncedCalendarProvider {
7
+ GOOGLE = "google",
8
+ OUTLOOK = "outlook",
9
+ APPLE = "apple",
10
+ }
11
+
12
+ /**
13
+ * Interface for synced calendar
14
+ */
15
+ export interface SyncedCalendar {
16
+ id: string;
17
+ provider: SyncedCalendarProvider;
18
+ name: string;
19
+ description?: string;
20
+ accessToken: string;
21
+ refreshToken: string;
22
+ tokenExpiry: Timestamp;
23
+ calendarId: string;
24
+ isActive: boolean;
25
+ lastSyncedAt?: Timestamp;
26
+ createdAt: Timestamp;
27
+ updatedAt: Timestamp;
28
+ }
29
+
30
+ /**
31
+ * Interface for creating a synced calendar
32
+ */
33
+ export interface CreateSyncedCalendarData {
34
+ id: string;
35
+ provider: SyncedCalendarProvider;
36
+ name: string;
37
+ description?: string;
38
+ accessToken: string;
39
+ refreshToken: string;
40
+ tokenExpiry: Timestamp;
41
+ calendarId: string;
42
+ isActive: boolean;
43
+ lastSyncedAt?: Timestamp;
44
+ createdAt: FieldValue;
45
+ updatedAt: FieldValue;
46
+ }
47
+
48
+ /**
49
+ * Interface for updating a synced calendar
50
+ */
51
+ export interface UpdateSyncedCalendarData {
52
+ name?: string;
53
+ description?: string;
54
+ accessToken?: string;
55
+ refreshToken?: string;
56
+ tokenExpiry?: Timestamp;
57
+ calendarId?: string;
58
+ isActive?: boolean;
59
+ lastSyncedAt?: Timestamp;
60
+ updatedAt: FieldValue;
61
+ }
62
+
63
+ /**
64
+ * Collection name for synced calendars
65
+ */
66
+ export const SYNCED_CALENDARS_COLLECTION = "syncedCalendars";
@@ -5,6 +5,7 @@ import type {
5
5
  Currency,
6
6
  PricingMeasure,
7
7
  } from "../../backoffice/types/static/pricing.types";
8
+ import type { ClinicInfo } from "../profile";
8
9
 
9
10
  export const CLINIC_GROUPS_COLLECTION = "clinic_groups";
10
11
  export const CLINIC_ADMINS_COLLECTION = "clinic_admins";
@@ -94,18 +95,6 @@ export interface ContactPerson {
94
95
  phoneNumber?: string | null;
95
96
  }
96
97
 
97
- /**
98
- * Interface for clinic information
99
- */
100
- export interface ClinicInfo {
101
- id: string;
102
- featuredPhoto: string;
103
- name: string;
104
- description?: string;
105
- location: ClinicLocation;
106
- contactInfo: ClinicContactInfo;
107
- }
108
-
109
98
  /**
110
99
  * Interface for clinic admin
111
100
  */
@@ -41,3 +41,7 @@ export type FirebaseUser = FirebaseAuthUser;
41
41
  export * from "./documentation-templates";
42
42
  export const DOCUMENTATION_TEMPLATES_COLLECTION = "documentation-templates";
43
43
  export const FILLED_DOCUMENTS_COLLECTION = "filled-documents";
44
+
45
+ // Calendar
46
+ export * from "./calendar";
47
+ export const CALENDAR_COLLECTION = "calendar";
@@ -35,6 +35,25 @@ export interface PractitionerCertification {
35
35
  verificationStatus: "pending" | "verified" | "rejected";
36
36
  }
37
37
 
38
+ /**
39
+ * Interfejs za radno vreme zdravstvenog radnika u klinici
40
+ */
41
+ export interface PractitionerClinicWorkingHours {
42
+ clinicId: string;
43
+ workingHours: {
44
+ monday: { start: string; end: string } | null;
45
+ tuesday: { start: string; end: string } | null;
46
+ wednesday: { start: string; end: string } | null;
47
+ thursday: { start: string; end: string } | null;
48
+ friday: { start: string; end: string } | null;
49
+ saturday: { start: string; end: string } | null;
50
+ sunday: { start: string; end: string } | null;
51
+ };
52
+ isActive: boolean;
53
+ createdAt: Timestamp;
54
+ updatedAt: Timestamp;
55
+ }
56
+
38
57
  /**
39
58
  * Interfejs za zdravstvenog radnika
40
59
  */
@@ -44,6 +63,7 @@ export interface Practitioner {
44
63
  basicInfo: PractitionerBasicInfo;
45
64
  certification: PractitionerCertification;
46
65
  clinics: string[]; // Reference na klinike gde radi
66
+ clinicWorkingHours: PractitionerClinicWorkingHours[]; // Radno vreme za svaku kliniku
47
67
  isActive: boolean;
48
68
  isVerified: boolean;
49
69
  createdAt: Timestamp;
@@ -58,6 +78,7 @@ export interface CreatePractitionerData {
58
78
  basicInfo: PractitionerBasicInfo;
59
79
  certification: PractitionerCertification;
60
80
  clinics?: string[];
81
+ clinicWorkingHours?: PractitionerClinicWorkingHours[];
61
82
  isActive: boolean;
62
83
  isVerified: boolean;
63
84
  }
@@ -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
+ });
@@ -60,6 +60,25 @@ export const practitionerWorkingHoursSchema = z.object({
60
60
  updatedAt: z.instanceof(Timestamp),
61
61
  });
62
62
 
63
+ /**
64
+ * Šema za validaciju radnog vremena u klinici
65
+ */
66
+ export const practitionerClinicWorkingHoursSchema = z.object({
67
+ clinicId: z.string().min(1),
68
+ workingHours: z.object({
69
+ monday: timeSlotSchema,
70
+ tuesday: timeSlotSchema,
71
+ wednesday: timeSlotSchema,
72
+ thursday: timeSlotSchema,
73
+ friday: timeSlotSchema,
74
+ saturday: timeSlotSchema,
75
+ sunday: timeSlotSchema,
76
+ }),
77
+ isActive: z.boolean(),
78
+ createdAt: z.instanceof(Timestamp),
79
+ updatedAt: z.instanceof(Timestamp),
80
+ });
81
+
63
82
  /**
64
83
  * Šema za validaciju recenzije zdravstvenog radnika
65
84
  */
@@ -96,6 +115,7 @@ export const practitionerSchema = z.object({
96
115
  basicInfo: practitionerBasicInfoSchema,
97
116
  certification: practitionerCertificationSchema,
98
117
  clinics: z.array(z.string()),
118
+ clinicWorkingHours: z.array(practitionerClinicWorkingHoursSchema),
99
119
  isActive: z.boolean(),
100
120
  isVerified: z.boolean(),
101
121
  createdAt: z.instanceof(Timestamp),
@@ -110,6 +130,7 @@ export const createPractitionerSchema = z.object({
110
130
  basicInfo: practitionerBasicInfoSchema,
111
131
  certification: practitionerCertificationSchema,
112
132
  clinics: z.array(z.string()).optional(),
133
+ clinicWorkingHours: z.array(practitionerClinicWorkingHoursSchema).optional(),
113
134
  isActive: z.boolean(),
114
135
  isVerified: z.boolean(),
115
136
  });
@@ -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
+ });