@blackcode_sa/metaestetics-api 1.6.3 → 1.6.5

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 (40) hide show
  1. package/dist/admin/index.d.mts +439 -25
  2. package/dist/admin/index.d.ts +439 -25
  3. package/dist/admin/index.js +36107 -2493
  4. package/dist/admin/index.mjs +36093 -2461
  5. package/dist/backoffice/index.d.mts +254 -1
  6. package/dist/backoffice/index.d.ts +254 -1
  7. package/dist/backoffice/index.js +86 -12
  8. package/dist/backoffice/index.mjs +86 -13
  9. package/dist/index.d.mts +1434 -621
  10. package/dist/index.d.ts +1434 -621
  11. package/dist/index.js +1381 -970
  12. package/dist/index.mjs +1433 -1016
  13. package/package.json +1 -1
  14. package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +321 -0
  15. package/src/admin/booking/booking.admin.ts +376 -3
  16. package/src/admin/index.ts +15 -1
  17. package/src/admin/notifications/notifications.admin.ts +1 -1
  18. package/src/admin/requirements/README.md +128 -0
  19. package/src/admin/requirements/patient-requirements.admin.service.ts +482 -0
  20. package/src/backoffice/types/product.types.ts +2 -0
  21. package/src/index.ts +16 -1
  22. package/src/services/appointment/appointment.service.ts +386 -250
  23. package/src/services/clinic/clinic-admin.service.ts +3 -0
  24. package/src/services/clinic/clinic-group.service.ts +8 -0
  25. package/src/services/documentation-templates/documentation-template.service.ts +24 -16
  26. package/src/services/documentation-templates/filled-document.service.ts +253 -136
  27. package/src/services/patient/patientRequirements.service.ts +285 -0
  28. package/src/services/procedure/procedure.service.ts +1 -0
  29. package/src/types/appointment/index.ts +136 -11
  30. package/src/types/documentation-templates/index.ts +34 -2
  31. package/src/types/notifications/README.md +77 -0
  32. package/src/types/notifications/index.ts +154 -27
  33. package/src/types/patient/patient-requirements.ts +81 -0
  34. package/src/types/procedure/index.ts +7 -0
  35. package/src/validations/appointment.schema.ts +298 -62
  36. package/src/validations/documentation-templates/template.schema.ts +55 -0
  37. package/src/validations/documentation-templates.schema.ts +9 -14
  38. package/src/validations/notification.schema.ts +3 -3
  39. package/src/validations/patient/patient-requirements.schema.ts +75 -0
  40. package/src/validations/procedure.schema.ts +3 -0
@@ -0,0 +1,285 @@
1
+ import {
2
+ collection,
3
+ getDocs,
4
+ query,
5
+ where,
6
+ doc,
7
+ updateDoc,
8
+ Timestamp,
9
+ DocumentReference,
10
+ serverTimestamp,
11
+ orderBy,
12
+ limit,
13
+ startAfter,
14
+ getDoc,
15
+ FieldPath,
16
+ WhereFilterOp,
17
+ Firestore,
18
+ DocumentSnapshot,
19
+ } from "firebase/firestore";
20
+ import { FirebaseApp } from "firebase/app";
21
+ import { Auth } from "firebase/auth";
22
+ import { BaseService } from "../base.service";
23
+ import {
24
+ PatientRequirementInstance,
25
+ PATIENT_REQUIREMENTS_SUBCOLLECTION_NAME,
26
+ PatientInstructionStatus,
27
+ PatientRequirementOverallStatus,
28
+ PatientRequirementInstruction,
29
+ } from "../../types/patient/patient-requirements";
30
+ import { UserRole } from "../../types"; // Assuming UserRole is in the root types
31
+
32
+ /**
33
+ * Interface for filtering active patient requirements.
34
+ */
35
+ export interface PatientRequirementsFilters {
36
+ appointmentId?: string | "all"; // Specific appointment, or 'all' for any appointment
37
+ statuses?: PatientRequirementOverallStatus[];
38
+ instructionStatuses?: PatientInstructionStatus[]; // Filter by status of individual instructions
39
+ dueBefore?: Timestamp; // Filter for instructions due before this time
40
+ dueAfter?: Timestamp; // Filter for instructions due after this time
41
+ }
42
+
43
+ export class PatientRequirementsService extends BaseService {
44
+ constructor(db: Firestore, auth: Auth, app: FirebaseApp) {
45
+ super(db, auth, app);
46
+ }
47
+
48
+ private getPatientRequirementsCollectionRef(patientId: string) {
49
+ return collection(
50
+ this.db,
51
+ `patients/${patientId}/${PATIENT_REQUIREMENTS_SUBCOLLECTION_NAME}`
52
+ );
53
+ }
54
+
55
+ private getPatientRequirementDocRef(
56
+ patientId: string,
57
+ instanceId: string
58
+ ): DocumentReference<PatientRequirementInstance> {
59
+ return doc(
60
+ this.getPatientRequirementsCollectionRef(patientId),
61
+ instanceId
62
+ ) as DocumentReference<PatientRequirementInstance>;
63
+ }
64
+
65
+ /**
66
+ * Gets a specific patient requirement instance by its ID.
67
+ * @param patientId - The ID of the patient.
68
+ * @param instanceId - The ID of the requirement instance.
69
+ * @returns The patient requirement instance or null if not found.
70
+ */
71
+ async getPatientRequirementInstance(
72
+ patientId: string,
73
+ instanceId: string
74
+ ): Promise<PatientRequirementInstance | null> {
75
+ const docRef = this.getPatientRequirementDocRef(patientId, instanceId);
76
+ const docSnap = await getDoc(docRef);
77
+ if (!docSnap.exists()) {
78
+ return null;
79
+ }
80
+ // Explicitly type data to exclude 'id' before spreading
81
+ const data = docSnap.data() as Omit<PatientRequirementInstance, "id">;
82
+ return { id: docSnap.id, ...data } as PatientRequirementInstance;
83
+ }
84
+
85
+ /**
86
+ * Retrieves patient requirement instances based on specified filters.
87
+ * This is a flexible query method.
88
+ *
89
+ * @param patientId - The ID of the patient.
90
+ * @param filters - Optional filters for appointmentId, overall statuses, instruction statuses, and due timeframes.
91
+ * @param pageLimit - Optional limit for pagination.
92
+ * @param lastVisible - Optional last document snapshot for pagination.
93
+ * @returns A promise resolving to an array of matching patient requirement instances and the last document snapshot.
94
+ */
95
+ async getAllPatientRequirementInstances(
96
+ patientId: string,
97
+ filters?: PatientRequirementsFilters,
98
+ pageLimit: number = 20,
99
+ lastVisible?: DocumentSnapshot
100
+ ): Promise<{
101
+ requirements: PatientRequirementInstance[];
102
+ lastDoc: DocumentSnapshot | null;
103
+ }> {
104
+ const collRef = this.getPatientRequirementsCollectionRef(patientId);
105
+ let q = query(collRef, orderBy("createdAt", "desc")); // Default sort
106
+
107
+ const queryConstraints = [];
108
+
109
+ if (filters?.appointmentId && filters.appointmentId !== "all") {
110
+ queryConstraints.push(
111
+ where("appointmentId", "==", filters.appointmentId)
112
+ );
113
+ }
114
+
115
+ if (filters?.statuses && filters.statuses.length > 0) {
116
+ queryConstraints.push(where("overallStatus", "in", filters.statuses));
117
+ }
118
+
119
+ // Filtering by instruction statuses or due times requires iterating post-fetch
120
+ // or more complex data modeling if direct querying is essential at scale.
121
+ // For a "light" service, post-fetch filtering for these is acceptable for now.
122
+
123
+ if (lastVisible) {
124
+ queryConstraints.push(startAfter(lastVisible));
125
+ }
126
+ queryConstraints.push(limit(pageLimit));
127
+
128
+ q = query(collRef, ...queryConstraints);
129
+
130
+ const snapshot = await getDocs(q);
131
+ let requirements = snapshot.docs.map((docSnap: DocumentSnapshot) => {
132
+ // Explicitly cast data after ensuring it's not undefined (though .data() on QueryDocumentSnapshot is not undefined)
133
+ const data = docSnap.data() as Omit<PatientRequirementInstance, "id">;
134
+ return { id: docSnap.id, ...data } as PatientRequirementInstance;
135
+ });
136
+
137
+ // Post-fetch filtering for instruction statuses and due times
138
+ if (
139
+ filters?.instructionStatuses &&
140
+ filters.instructionStatuses.length > 0
141
+ ) {
142
+ requirements = requirements.filter((req) =>
143
+ req.instructions.some((instr) =>
144
+ filters.instructionStatuses!.includes(instr.status)
145
+ )
146
+ );
147
+ }
148
+
149
+ if (filters?.dueBefore) {
150
+ const dueBeforeMillis = filters.dueBefore.toMillis();
151
+ requirements = requirements.filter((req) =>
152
+ req.instructions.some(
153
+ (instr) => instr.dueTime.toMillis() < dueBeforeMillis
154
+ )
155
+ );
156
+ }
157
+
158
+ if (filters?.dueAfter) {
159
+ const dueAfterMillis = filters.dueAfter.toMillis();
160
+ requirements = requirements.filter((req) =>
161
+ req.instructions.some(
162
+ (instr) => instr.dueTime.toMillis() > dueAfterMillis
163
+ )
164
+ );
165
+ }
166
+
167
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1] || null;
168
+
169
+ return { requirements, lastDoc: newLastVisible };
170
+ }
171
+
172
+ /**
173
+ * Marks a specific instruction within a PatientRequirementInstance as ACTION_TAKEN.
174
+ * If all instructions are actioned, updates the overallStatus of the instance.
175
+ *
176
+ * @param patientId - The ID of the patient.
177
+ * @param instanceId - The ID of the PatientRequirementInstance.
178
+ * @param instructionId - The ID of the instruction to complete.
179
+ * @returns The updated PatientRequirementInstance.
180
+ * @throws Error if the instance or instruction is not found, or if the instruction is not in a completable state.
181
+ */
182
+ async completeInstruction(
183
+ patientId: string,
184
+ instanceId: string,
185
+ instructionId: string
186
+ ): Promise<PatientRequirementInstance> {
187
+ const instanceRef = this.getPatientRequirementDocRef(patientId, instanceId);
188
+ const instanceSnap = await getDoc(instanceRef); // Simplified: getDoc without explicit generic here
189
+
190
+ if (!instanceSnap.exists()) {
191
+ throw new Error(
192
+ `PatientRequirementInstance ${instanceId} not found for patient ${patientId}.`
193
+ );
194
+ }
195
+
196
+ // Explicitly cast data after exists check
197
+ const instanceData = instanceSnap.data() as Omit<
198
+ PatientRequirementInstance,
199
+ "id"
200
+ >;
201
+ const instance = { id: instanceSnap.id, ...instanceData };
202
+
203
+ const instructionIndex = instance.instructions.findIndex(
204
+ (instr) => instr.instructionId === instructionId
205
+ );
206
+
207
+ if (instructionIndex === -1) {
208
+ throw new Error(
209
+ `Instruction ${instructionId} not found in instance ${instanceId}.`
210
+ );
211
+ }
212
+
213
+ const instructionToUpdate = instance.instructions[instructionIndex];
214
+
215
+ // Allow completion if it's PENDING_NOTIFICATION, ACTION_DUE, or even MISSED (if policy allows late completion)
216
+ if (
217
+ instructionToUpdate.status !==
218
+ PatientInstructionStatus.PENDING_NOTIFICATION &&
219
+ instructionToUpdate.status !== PatientInstructionStatus.ACTION_DUE &&
220
+ instructionToUpdate.status !== PatientInstructionStatus.MISSED
221
+ ) {
222
+ // If already ACTION_TAKEN or CANCELLED, do nothing or throw specific error
223
+ if (
224
+ instructionToUpdate.status === PatientInstructionStatus.ACTION_TAKEN
225
+ ) {
226
+ console.warn(
227
+ `Instruction ${instructionId} is already marked as ACTION_TAKEN.`
228
+ );
229
+ return instance as PatientRequirementInstance; // Ensure return type matches
230
+ }
231
+ throw new Error(
232
+ `Instruction ${instructionId} is in status ${instructionToUpdate.status} and cannot be marked as completed.`
233
+ );
234
+ }
235
+
236
+ const now = Timestamp.now();
237
+ const updatedInstructions = [...instance.instructions];
238
+ updatedInstructions[instructionIndex] = {
239
+ ...instructionToUpdate,
240
+ status: PatientInstructionStatus.ACTION_TAKEN,
241
+ actionTakenAt: now,
242
+ updatedAt: now,
243
+ };
244
+
245
+ // Check if all instructions are now ACTION_TAKEN
246
+ const allActionTaken = updatedInstructions.every(
247
+ (instr) => instr.status === PatientInstructionStatus.ACTION_TAKEN
248
+ );
249
+
250
+ let newOverallStatus = instance.overallStatus;
251
+ if (allActionTaken) {
252
+ newOverallStatus = PatientRequirementOverallStatus.ALL_INSTRUCTIONS_MET;
253
+ } else if (
254
+ updatedInstructions.some(
255
+ (instr) => instr.status === PatientInstructionStatus.ACTION_TAKEN
256
+ )
257
+ ) {
258
+ // If some are taken, but not all, and it was previously just ACTIVE
259
+ newOverallStatus = PatientRequirementOverallStatus.PARTIALLY_COMPLETED;
260
+ }
261
+
262
+ const updatePayload: { [key: string]: any } = {
263
+ // Using a general type for updateDoc payload
264
+ instructions: updatedInstructions,
265
+ updatedAt: now,
266
+ };
267
+
268
+ if (newOverallStatus !== instance.overallStatus) {
269
+ updatePayload.overallStatus = newOverallStatus;
270
+ }
271
+
272
+ await updateDoc(instanceRef, updatePayload);
273
+
274
+ // Construct the returned object accurately
275
+ return {
276
+ ...instance,
277
+ instructions: updatedInstructions,
278
+ updatedAt: now,
279
+ overallStatus: newOverallStatus,
280
+ } as PatientRequirementInstance;
281
+ }
282
+
283
+ // Note: As per the request, full CRUD (create, direct update of instance, delete) is not part of this light service,
284
+ // as those will be handled by Cloud Functions reacting to appointment lifecycle events.
285
+ }
@@ -179,6 +179,7 @@ export class ProcedureService extends BaseService {
179
179
  technology,
180
180
  product,
181
181
  blockingConditions: technology.blockingConditions,
182
+ contraindications: technology.contraindications || [],
182
183
  treatmentBenefits: technology.benefits,
183
184
  preRequirements: technology.requirements.pre,
184
185
  postRequirements: technology.requirements.post,
@@ -6,24 +6,26 @@ import {
6
6
  } from "../profile";
7
7
  import { ProcedureSummaryInfo } from "../procedure";
8
8
  import { Currency } from "../../backoffice/types/static/pricing.types";
9
- import { CalendarEventStatus } from "../calendar";
10
9
  import { BlockingCondition } from "../../backoffice/types/static/blocking-condition.types";
11
10
  import { Contraindication } from "../../backoffice/types/static/contraindication.types";
12
11
  import { Requirement } from "../../backoffice/types/requirement.types";
12
+ import { FilledDocumentStatus } from "../documentation-templates";
13
+ import type { ProcedureFamily } from "../../backoffice";
13
14
 
14
15
  /**
15
16
  * Enum defining the possible statuses of an appointment.
16
17
  */
17
18
  export enum AppointmentStatus {
18
- SCHEDULED = "scheduled", // Initial state after booking, before confirmation (if applicable)
19
+ PENDING = "pending", // Initial state after booking, before confirmation (if applicable)
19
20
  CONFIRMED = "confirmed", // Confirmed by clinic/practitioner
20
21
  CHECKED_IN = "checked_in", // Patient has arrived
21
22
  IN_PROGRESS = "in_progress", // Procedure has started
22
23
  COMPLETED = "completed", // Procedure finished successfully
23
24
  CANCELED_PATIENT = "canceled_patient", // Canceled by the patient
25
+ CANCELED_PATIENT_RESCHEDULED = "canceled_patient_rescheduled", // Canceled by the patient and rescheduled by the clinic
24
26
  CANCELED_CLINIC = "canceled_clinic", // Canceled by the clinic/practitioner
25
27
  NO_SHOW = "no_show", // Patient did not attend
26
- RESCHEDULED = "rescheduled", // Original appointment replaced by a new one
28
+ RESCHEDULED_BY_CLINIC = "rescheduled_by_clinic", // When appointment is rescheduled by the clinic, waiting for patient confirmation or cancellation (when reschedule is accepted, status goes to confirmed, if not accepted, then status goes to canceled_patient_rescheduled)
27
29
  }
28
30
 
29
31
  /**
@@ -37,6 +39,76 @@ export enum PaymentStatus {
37
39
  NOT_APPLICABLE = "not_applicable", // For free services or other scenarios
38
40
  }
39
41
 
42
+ /**
43
+ * Enum for different types of media that can be attached to an appointment.
44
+ */
45
+ export enum MediaType {
46
+ BEFORE_PHOTO = "before_photo",
47
+ AFTER_PHOTO = "after_photo",
48
+ CONSENT_SCAN = "consent_scan",
49
+ OTHER_DOCUMENT = "other_document",
50
+ }
51
+
52
+ /**
53
+ * Interface to describe a media file linked to an appointment.
54
+ */
55
+ export interface AppointmentMediaItem {
56
+ id: string; // Auto-generated unique ID for the media item
57
+ type: MediaType;
58
+ url: string; // Cloud Storage URL
59
+ fileName?: string;
60
+ uploadedAt: Timestamp;
61
+ uploadedBy: string; // User ID (patient, practitioner, or clinic_admin)
62
+ description?: string;
63
+ }
64
+
65
+ /**
66
+ * Interface for procedure-specific information
67
+ */
68
+ export interface ProcedureExtendedInfo {
69
+ id: string;
70
+ name: string;
71
+ description: string;
72
+ cost: number;
73
+ duration: number;
74
+ procedureFamily: ProcedureFamily;
75
+ procedureCategoryId: string;
76
+ procedureCategoryName: string;
77
+ procedureSubCategoryId: string;
78
+ procedureSubCategoryName: string;
79
+ procedureTechnologyId: string;
80
+ procedureTechnologyName: string;
81
+ procedureProductBrandId: string;
82
+ procedureProductBrandName: string;
83
+ procedureProductId: string;
84
+ procedureProductName: string;
85
+ }
86
+
87
+ /**
88
+ * Interface to describe a filled form linked to an appointment.
89
+ */
90
+ export interface LinkedFormInfo {
91
+ formId: string; // ID of the FilledDocument
92
+ templateId: string;
93
+ templateVersion: number;
94
+ title: string; // For display, usually from DocumentTemplate.title
95
+ isUserForm: boolean;
96
+ status: FilledDocumentStatus; // Status of the filled form (e.g., draft, completed, signed)
97
+ path: string; // Full Firestore path to the filled document (e.g., appointments/{aid}/user-forms/{fid})
98
+ submittedAt?: Timestamp;
99
+ completedAt?: Timestamp; // When the form reached a final state like 'completed' or 'signed'
100
+ }
101
+
102
+ /**
103
+ * Interface for summarized patient review information linked to an appointment.
104
+ */
105
+ export interface PatientReviewInfo {
106
+ reviewId: string; // ID of the full review document/record if stored elsewhere
107
+ rating: number; // e.g., 1-5 stars
108
+ comment?: string; // A short snippet or the full comment
109
+ reviewedAt: Timestamp;
110
+ }
111
+
40
112
  /**
41
113
  * Represents a booked appointment, aggregating key information and relevant procedure rules.
42
114
  */
@@ -64,7 +136,9 @@ export interface Appointment {
64
136
  /** ID of the procedure */
65
137
  procedureId: string;
66
138
  /** Aggregated procedure information including product/brand (snapshot) */
67
- procedureInfo: ProcedureSummaryInfo;
139
+ procedureInfo: ProcedureSummaryInfo; // Aggregated procedure information
140
+ /** Extended procedure information */
141
+ procedureExtendedInfo: ProcedureExtendedInfo; // Aggregated extended procedure information
68
142
 
69
143
  /** Status of the appointment */
70
144
  status: AppointmentStatus;
@@ -72,8 +146,11 @@ export interface Appointment {
72
146
  /** Timestamps */
73
147
  bookingTime: Timestamp;
74
148
  confirmationTime?: Timestamp | null;
149
+ cancellationTime?: Timestamp | null;
150
+ rescheduleTime?: Timestamp | null;
75
151
  appointmentStartTime: Timestamp;
76
152
  appointmentEndTime: Timestamp;
153
+ procedureActualStartTime?: Timestamp | null; // NEW: Actual start time of the procedure
77
154
  actualDurationMinutes?: number;
78
155
 
79
156
  /** Cancellation Details */
@@ -100,20 +177,40 @@ export interface Appointment {
100
177
  completedPreRequirements?: string[]; // IDs of completed pre-requirements
101
178
  completedPostRequirements?: string[]; // IDs of completed post-requirements
102
179
 
103
- /** Timestamps */
180
+ /** NEW: Linked forms (consent, procedure-specific forms, etc.) */
181
+ linkedFormIds?: string[];
182
+ linkedForms?: LinkedFormInfo[];
183
+ pendingUserFormsIds?: string[]; // Determines if there are any user forms that are pending for this appointment, blocks the appointment from being checked in (only for user forms with isRequired = true)
184
+
185
+ /** NEW: Media items (before/after photos, scanned documents, etc.) */
186
+ media?: AppointmentMediaItem[];
187
+
188
+ /** NEW: Information about the patient's review for this appointment */
189
+ reviewInfo?: PatientReviewInfo | null;
190
+
191
+ /** NEW: Details about the finalization of the appointment by the practitioner */
192
+ finalizedDetails?: {
193
+ by: string; // Practitioner User ID
194
+ at: Timestamp;
195
+ notes?: string;
196
+ };
197
+
198
+ /** Timestamps for record creation and updates */
104
199
  createdAt: Timestamp;
105
200
  updatedAt: Timestamp;
106
201
 
107
202
  /** Recurring appointment information */
108
203
  isRecurring?: boolean;
109
204
  recurringAppointmentId?: string | null;
205
+
206
+ /** NEW: Flag for soft deletion or archiving */
207
+ isArchived?: boolean;
110
208
  }
111
209
 
112
210
  /**
113
211
  * Data needed to create a new Appointment
114
212
  */
115
213
  export interface CreateAppointmentData {
116
- calendarEventId: string;
117
214
  clinicBranchId: string;
118
215
  practitionerId: string;
119
216
  patientId: string;
@@ -124,7 +221,7 @@ export interface CreateAppointmentData {
124
221
  currency: Currency;
125
222
  patientNotes?: string | null;
126
223
  initialStatus: AppointmentStatus;
127
- initialPaymentStatus?: PaymentStatus;
224
+ initialPaymentStatus?: PaymentStatus; // Defaults to UNPAID if not provided
128
225
  }
129
226
 
130
227
  /**
@@ -132,15 +229,43 @@ export interface CreateAppointmentData {
132
229
  */
133
230
  export interface UpdateAppointmentData {
134
231
  status?: AppointmentStatus;
135
- confirmationTime?: Timestamp | null;
232
+ confirmationTime?: Timestamp | FieldValue | null;
233
+ cancellationTime?: Timestamp | FieldValue | null;
234
+ rescheduleTime?: Timestamp | FieldValue | null;
235
+ procedureActualStartTime?: Timestamp | FieldValue | null; // NEW
136
236
  actualDurationMinutes?: number;
137
237
  cancellationReason?: string | null;
138
238
  canceledBy?: "patient" | "clinic" | "practitioner" | "system";
139
239
  internalNotes?: string | null;
240
+ patientNotes?: string | FieldValue | null; // Allow FieldValue for deleting
140
241
  paymentStatus?: PaymentStatus;
141
- paymentTransactionId?: string | null;
142
- completedPreRequirements?: string[]; // IDs of pre-requirements to mark as completed
143
- completedPostRequirements?: string[]; // IDs of post-requirements to mark as completed
242
+ paymentTransactionId?: string | FieldValue | null;
243
+ completedPreRequirements?: string[] | FieldValue; // Allow FieldValue for arrayUnion/arrayRemove
244
+ completedPostRequirements?: string[] | FieldValue;
245
+ appointmentStartTime?: Timestamp; // For rescheduling
246
+ appointmentEndTime?: Timestamp; // For rescheduling
247
+ calendarEventId?: string; // If calendar event needs to be re-linked
248
+ cost?: number; // If cost is adjusted
249
+ clinicBranchId?: string; // If appointment is moved to another branch (complex scenario)
250
+ practitionerId?: string; // If practitioner is changed
251
+
252
+ /** NEW: For updating linked forms - typically managed by dedicated methods */
253
+ linkedFormIds?: string[] | FieldValue;
254
+ linkedForms?: LinkedFormInfo[] | FieldValue;
255
+
256
+ /** NEW: For updating media items - typically managed by dedicated methods */
257
+ media?: AppointmentMediaItem[] | FieldValue;
258
+
259
+ /** NEW: For adding/updating review information */
260
+ reviewInfo?: PatientReviewInfo | FieldValue | null;
261
+
262
+ /** NEW: For setting practitioner finalization details */
263
+ finalizedDetails?: { by: string; at: Timestamp; notes?: string } | FieldValue;
264
+
265
+ /** NEW: For archiving/unarchiving */
266
+ isArchived?: boolean;
267
+
268
+ updatedAt?: FieldValue; // To set server timestamp
144
269
  }
145
270
 
146
271
  /**
@@ -7,6 +7,8 @@
7
7
  */
8
8
  export const DOCUMENTATION_TEMPLATES_COLLECTION = "documentation-templates";
9
9
  export const FILLED_DOCUMENTS_COLLECTION = "filled-documents";
10
+ export const USER_FORMS_SUBCOLLECTION = "user-forms";
11
+ export const DOCTOR_FORMS_SUBCOLLECTION = "doctor-forms";
10
12
  /**
11
13
  * Enum for element types in documentation templates
12
14
  */
@@ -27,6 +29,7 @@ export enum DocumentElementType {
27
29
  TEXT_INPUT = "text_input",
28
30
  DATE_PICKER = "date_picker",
29
31
  SIGNATURE = "signature",
32
+ DITIGAL_SIGNATURE = "digital_signature",
30
33
  FILE_UPLOAD = "file_upload",
31
34
  }
32
35
 
@@ -60,6 +63,11 @@ export enum DynamicVariable {
60
63
  PATIENT_BIRTHDAY = "[PATIENT_BIRTHDAY]",
61
64
  APPOINTMENT_DATE = "[APPOINTMENT_DATE]",
62
65
  CURRENT_DATE = "[CURRENT_DATE]",
66
+ PROCEDURE_NAME = "[PROCEDURE_NAME]",
67
+ PROCEDURE_DESCRIPTION = "[PROCEDURE_DESCRIPTION]",
68
+ PROCEDURE_COST = "[PROCEDURE_COST]",
69
+ PROCEDURE_DURATION = "[PROCEDURE_DURATION]",
70
+ PROCEDURE_RISK = "[PROCEDURE_RISK]",
63
71
  }
64
72
 
65
73
  /**
@@ -174,6 +182,14 @@ export interface SignatureElement extends BaseDocumentElement {
174
182
  label: string;
175
183
  }
176
184
 
185
+ /**
186
+ * Interface for digital signature element
187
+ */
188
+ export interface DigitalSignatureElement extends BaseDocumentElement {
189
+ type: DocumentElementType.DITIGAL_SIGNATURE;
190
+ label: string;
191
+ }
192
+
177
193
  /**
178
194
  * Interface for file upload element
179
195
  */
@@ -213,6 +229,9 @@ export interface DocumentTemplate {
213
229
  createdBy: string; // User ID
214
230
  elements: DocumentElement[];
215
231
  tags?: string[]; // For categorization
232
+ isUserForm?: boolean; // Default is false, and that means it's a doctor form
233
+ isRequired?: boolean; // Default is false, and that means it's an optional form, this is mostly used for user forms
234
+ sortingOrder?: number; // For sorting the forms in the UI, default is 0
216
235
  version: number; // For versioning
217
236
  isActive: boolean; // Whether the template is active
218
237
  }
@@ -225,6 +244,9 @@ export interface CreateDocumentTemplateData {
225
244
  description?: string;
226
245
  elements: Omit<DocumentElement, "id">[];
227
246
  tags?: string[];
247
+ isUserForm?: boolean; // Default is false, and that means it's a doctor form
248
+ isRequired?: boolean; // Default is false, and that means it's an optional form, this is mostly used for user forms
249
+ sortingOrder?: number; // For sorting the forms in the UI, default is 0
228
250
  }
229
251
 
230
252
  /**
@@ -236,6 +258,9 @@ export interface UpdateDocumentTemplateData {
236
258
  elements?: Omit<DocumentElement, "id">[];
237
259
  tags?: string[];
238
260
  isActive?: boolean;
261
+ isUserForm?: boolean; // Default is false, and that means it's a doctor form
262
+ isRequired?: boolean; // Default is false, and that means it's an optional form, this is mostly used for user forms
263
+ sortingOrder?: number; // For sorting the forms in the UI, default is 0
239
264
  }
240
265
 
241
266
  /**
@@ -245,6 +270,10 @@ export interface FilledDocument {
245
270
  id: string;
246
271
  templateId: string;
247
272
  templateVersion: number;
273
+ isUserForm: boolean;
274
+ isRequired: boolean;
275
+ procedureId: string;
276
+ appointmentId: string;
248
277
  patientId: string;
249
278
  practitionerId: string;
250
279
  clinicId: string;
@@ -259,6 +288,9 @@ export interface FilledDocument {
259
288
  */
260
289
  export enum FilledDocumentStatus {
261
290
  DRAFT = "draft",
262
- COMPLETED = "completed",
263
- SIGNED = "signed",
291
+ SKIPPED = "skipped",
292
+ PENDING = "pending",
293
+ COMPLETED = "completed", // When doctor or patient completes the form
294
+ SIGNED = "signed", // Only used for user forms
295
+ REJECTED = "rejected", // Only used for user forms
264
296
  }