@blackcode_sa/metaestetics-api 1.5.2 → 1.5.4
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/dist/backoffice/index.d.mts +1306 -63
- package/dist/backoffice/index.d.ts +1306 -63
- package/dist/backoffice/index.js +35 -26
- package/dist/backoffice/index.mjs +35 -26
- package/dist/index.d.mts +30 -16
- package/dist/index.d.ts +30 -16
- package/dist/index.js +81 -1
- package/dist/index.mjs +81 -1
- package/package.json +1 -1
- package/src/backoffice/services/brand.service.ts +2 -4
- package/src/backoffice/services/category.service.ts +2 -7
- package/src/backoffice/services/product.service.ts +5 -7
- package/src/backoffice/services/requirement.service.ts +6 -7
- package/src/backoffice/services/subcategory.service.ts +11 -8
- package/src/backoffice/services/technology.service.ts +6 -11
- package/src/backoffice/types/brand.types.ts +5 -0
- package/src/backoffice/types/category.types.ts +5 -0
- package/src/backoffice/types/documentation-templates.types.ts +4 -0
- package/src/backoffice/types/product.types.ts +5 -0
- package/src/backoffice/types/requirement.types.ts +5 -0
- package/src/backoffice/types/subcategory.types.ts +5 -0
- package/src/backoffice/types/technology.types.ts +10 -0
- package/src/backoffice/validations/schemas.ts +2 -0
- package/src/errors/auth.errors.ts +7 -0
- package/src/services/auth.service.ts +94 -0
- package/src/services/documentation-templates/documentation-template.service.ts +4 -1
- package/src/services/procedure/procedure.service.ts +238 -0
- package/src/types/documentation-templates/index.ts +5 -0
- package/src/types/index.ts +3 -3
- package/src/types/procedure/index.ts +104 -0
- package/src/validations/procedure.schema.ts +58 -0
- package/src/validations/schemas.ts +9 -3
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collection,
|
|
3
|
+
doc,
|
|
4
|
+
getDoc,
|
|
5
|
+
getDocs,
|
|
6
|
+
query,
|
|
7
|
+
where,
|
|
8
|
+
updateDoc,
|
|
9
|
+
setDoc,
|
|
10
|
+
deleteDoc,
|
|
11
|
+
Timestamp,
|
|
12
|
+
serverTimestamp,
|
|
13
|
+
DocumentData,
|
|
14
|
+
} from "firebase/firestore";
|
|
15
|
+
import { BaseService } from "../base.service";
|
|
16
|
+
import {
|
|
17
|
+
Procedure,
|
|
18
|
+
CreateProcedureData,
|
|
19
|
+
UpdateProcedureData,
|
|
20
|
+
PROCEDURES_COLLECTION,
|
|
21
|
+
} from "../../types/procedure";
|
|
22
|
+
import {
|
|
23
|
+
createProcedureSchema,
|
|
24
|
+
updateProcedureSchema,
|
|
25
|
+
} from "../../validations/procedure.schema";
|
|
26
|
+
import { z } from "zod";
|
|
27
|
+
import { Auth } from "firebase/auth";
|
|
28
|
+
import { Firestore } from "firebase/firestore";
|
|
29
|
+
import { FirebaseApp } from "firebase/app";
|
|
30
|
+
import {
|
|
31
|
+
Category,
|
|
32
|
+
CATEGORIES_COLLECTION,
|
|
33
|
+
} from "../../backoffice/types/category.types";
|
|
34
|
+
import {
|
|
35
|
+
Subcategory,
|
|
36
|
+
SUBCATEGORIES_COLLECTION,
|
|
37
|
+
} from "../../backoffice/types/subcategory.types";
|
|
38
|
+
import {
|
|
39
|
+
Technology,
|
|
40
|
+
TECHNOLOGIES_COLLECTION,
|
|
41
|
+
} from "../../backoffice/types/technology.types";
|
|
42
|
+
import {
|
|
43
|
+
Product,
|
|
44
|
+
PRODUCTS_COLLECTION,
|
|
45
|
+
} from "../../backoffice/types/product.types";
|
|
46
|
+
|
|
47
|
+
export class ProcedureService extends BaseService {
|
|
48
|
+
constructor(db: Firestore, auth: Auth, app: FirebaseApp) {
|
|
49
|
+
super(db, auth, app);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Creates a new procedure
|
|
54
|
+
* @param data - The data for creating a new procedure
|
|
55
|
+
* @returns The created procedure
|
|
56
|
+
*/
|
|
57
|
+
async createProcedure(data: CreateProcedureData): Promise<Procedure> {
|
|
58
|
+
// Validate input data
|
|
59
|
+
const validatedData = createProcedureSchema.parse(data);
|
|
60
|
+
|
|
61
|
+
// Get references to related entities
|
|
62
|
+
const [category, subcategory, technology, product] = await Promise.all([
|
|
63
|
+
this.getCategory(validatedData.categoryId),
|
|
64
|
+
this.getSubcategory(validatedData.subcategoryId),
|
|
65
|
+
this.getTechnology(validatedData.technologyId),
|
|
66
|
+
this.getProduct(validatedData.productId),
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
if (!category || !subcategory || !technology || !product) {
|
|
70
|
+
throw new Error("One or more required entities not found");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Create the procedure object
|
|
74
|
+
const procedure: Omit<Procedure, "id"> = {
|
|
75
|
+
...validatedData,
|
|
76
|
+
category,
|
|
77
|
+
subcategory,
|
|
78
|
+
technology,
|
|
79
|
+
product,
|
|
80
|
+
blockingConditions: technology.blockingConditions,
|
|
81
|
+
treatmentBenefits: technology.benefits,
|
|
82
|
+
preRequirements: technology.requirements.pre,
|
|
83
|
+
postRequirements: technology.requirements.post,
|
|
84
|
+
certificationRequirement: technology.certificationRequirement,
|
|
85
|
+
documentationTemplates: technology.documentationTemplates || [],
|
|
86
|
+
isActive: true,
|
|
87
|
+
createdAt: new Date(),
|
|
88
|
+
updatedAt: new Date(),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Generate ID and create document
|
|
92
|
+
const id = this.generateId();
|
|
93
|
+
const docRef = doc(this.db, PROCEDURES_COLLECTION, id);
|
|
94
|
+
await setDoc(docRef, {
|
|
95
|
+
...procedure,
|
|
96
|
+
id,
|
|
97
|
+
createdAt: serverTimestamp(),
|
|
98
|
+
updatedAt: serverTimestamp(),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return { ...procedure, id } as Procedure;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Gets a procedure by ID
|
|
106
|
+
* @param id - The ID of the procedure to get
|
|
107
|
+
* @returns The procedure if found, null otherwise
|
|
108
|
+
*/
|
|
109
|
+
async getProcedure(id: string): Promise<Procedure | null> {
|
|
110
|
+
const docRef = doc(this.db, PROCEDURES_COLLECTION, id);
|
|
111
|
+
const docSnap = await getDoc(docRef);
|
|
112
|
+
|
|
113
|
+
if (!docSnap.exists()) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return docSnap.data() as Procedure;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Gets all procedures for a clinic branch
|
|
122
|
+
* @param clinicBranchId - The ID of the clinic branch
|
|
123
|
+
* @returns List of procedures
|
|
124
|
+
*/
|
|
125
|
+
async getProceduresByClinicBranch(
|
|
126
|
+
clinicBranchId: string
|
|
127
|
+
): Promise<Procedure[]> {
|
|
128
|
+
const q = query(
|
|
129
|
+
collection(this.db, PROCEDURES_COLLECTION),
|
|
130
|
+
where("clinicBranchId", "==", clinicBranchId),
|
|
131
|
+
where("isActive", "==", true)
|
|
132
|
+
);
|
|
133
|
+
const snapshot = await getDocs(q);
|
|
134
|
+
return snapshot.docs.map((doc) => doc.data() as Procedure);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Gets all procedures for a practitioner
|
|
139
|
+
* @param practitionerId - The ID of the practitioner
|
|
140
|
+
* @returns List of procedures
|
|
141
|
+
*/
|
|
142
|
+
async getProceduresByPractitioner(
|
|
143
|
+
practitionerId: string
|
|
144
|
+
): Promise<Procedure[]> {
|
|
145
|
+
const q = query(
|
|
146
|
+
collection(this.db, PROCEDURES_COLLECTION),
|
|
147
|
+
where("practitionerId", "==", practitionerId),
|
|
148
|
+
where("isActive", "==", true)
|
|
149
|
+
);
|
|
150
|
+
const snapshot = await getDocs(q);
|
|
151
|
+
return snapshot.docs.map((doc) => doc.data() as Procedure);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Updates a procedure
|
|
156
|
+
* @param id - The ID of the procedure to update
|
|
157
|
+
* @param data - The data to update
|
|
158
|
+
* @returns The updated procedure
|
|
159
|
+
*/
|
|
160
|
+
async updateProcedure(
|
|
161
|
+
id: string,
|
|
162
|
+
data: UpdateProcedureData
|
|
163
|
+
): Promise<Procedure> {
|
|
164
|
+
// Validate input data
|
|
165
|
+
const validatedData = updateProcedureSchema.parse(data);
|
|
166
|
+
|
|
167
|
+
// Get the existing procedure
|
|
168
|
+
const existingProcedure = await this.getProcedure(id);
|
|
169
|
+
if (!existingProcedure) {
|
|
170
|
+
throw new Error(`Procedure with ID ${id} not found`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Update the procedure
|
|
174
|
+
const docRef = doc(this.db, PROCEDURES_COLLECTION, id);
|
|
175
|
+
await updateDoc(docRef, {
|
|
176
|
+
...validatedData,
|
|
177
|
+
updatedAt: serverTimestamp(),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
...existingProcedure,
|
|
182
|
+
...validatedData,
|
|
183
|
+
updatedAt: new Date(),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Deactivates a procedure
|
|
189
|
+
* @param id - The ID of the procedure to deactivate
|
|
190
|
+
*/
|
|
191
|
+
async deactivateProcedure(id: string): Promise<void> {
|
|
192
|
+
const docRef = doc(this.db, PROCEDURES_COLLECTION, id);
|
|
193
|
+
await updateDoc(docRef, {
|
|
194
|
+
isActive: false,
|
|
195
|
+
updatedAt: serverTimestamp(),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Gets a category by ID
|
|
201
|
+
* @private
|
|
202
|
+
*/
|
|
203
|
+
private async getCategory(id: string): Promise<Category | null> {
|
|
204
|
+
const docRef = doc(this.db, CATEGORIES_COLLECTION, id);
|
|
205
|
+
const docSnap = await getDoc(docRef);
|
|
206
|
+
return docSnap.exists() ? (docSnap.data() as Category) : null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Gets a subcategory by ID
|
|
211
|
+
* @private
|
|
212
|
+
*/
|
|
213
|
+
private async getSubcategory(id: string): Promise<Subcategory | null> {
|
|
214
|
+
const docRef = doc(this.db, SUBCATEGORIES_COLLECTION, id);
|
|
215
|
+
const docSnap = await getDoc(docRef);
|
|
216
|
+
return docSnap.exists() ? (docSnap.data() as Subcategory) : null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Gets a technology by ID
|
|
221
|
+
* @private
|
|
222
|
+
*/
|
|
223
|
+
private async getTechnology(id: string): Promise<Technology | null> {
|
|
224
|
+
const docRef = doc(this.db, TECHNOLOGIES_COLLECTION, id);
|
|
225
|
+
const docSnap = await getDoc(docRef);
|
|
226
|
+
return docSnap.exists() ? (docSnap.data() as Technology) : null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Gets a product by ID
|
|
231
|
+
* @private
|
|
232
|
+
*/
|
|
233
|
+
private async getProduct(id: string): Promise<Product | null> {
|
|
234
|
+
const docRef = doc(this.db, PRODUCTS_COLLECTION, id);
|
|
235
|
+
const docSnap = await getDoc(docRef);
|
|
236
|
+
return docSnap.exists() ? (docSnap.data() as Product) : null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
* Types for the Medical Documentation Templating System
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Kolekcija u Firestore bazi gde se čuvaju dokumentacijske šablone
|
|
7
|
+
*/
|
|
8
|
+
export const DOCUMENTATION_TEMPLATES_COLLECTION = "documentation-templates";
|
|
9
|
+
export const FILLED_DOCUMENTS_COLLECTION = "filled-documents";
|
|
5
10
|
/**
|
|
6
11
|
* Enum for element types in documentation templates
|
|
7
12
|
*/
|
package/src/types/index.ts
CHANGED
|
@@ -14,9 +14,9 @@ export interface User {
|
|
|
14
14
|
email: string | null;
|
|
15
15
|
roles: UserRole[];
|
|
16
16
|
isAnonymous: boolean;
|
|
17
|
-
createdAt: Timestamp;
|
|
18
|
-
updatedAt: Timestamp;
|
|
19
|
-
lastLoginAt: Timestamp;
|
|
17
|
+
createdAt: Timestamp | FieldValue;
|
|
18
|
+
updatedAt: Timestamp | FieldValue;
|
|
19
|
+
lastLoginAt: Timestamp | FieldValue;
|
|
20
20
|
patientProfile?: string;
|
|
21
21
|
practitionerProfile?: string;
|
|
22
22
|
adminProfile?: string;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { ProcedureFamily } from "../../backoffice/types/static/procedure-family.types";
|
|
2
|
+
import { Category } from "../../backoffice/types/category.types";
|
|
3
|
+
import { Subcategory } from "../../backoffice/types/subcategory.types";
|
|
4
|
+
import { Technology } from "../../backoffice/types/technology.types";
|
|
5
|
+
import { Product } from "../../backoffice/types/product.types";
|
|
6
|
+
import {
|
|
7
|
+
PricingMeasure,
|
|
8
|
+
Currency,
|
|
9
|
+
} from "../../backoffice/types/static/pricing.types";
|
|
10
|
+
import { Requirement } from "../../backoffice/types/requirement.types";
|
|
11
|
+
import { BlockingCondition } from "../../backoffice/types/static/blocking-condition.types";
|
|
12
|
+
import { TreatmentBenefit } from "../../backoffice/types/static/treatment-benefit.types";
|
|
13
|
+
import { CertificationRequirement } from "../../backoffice/types/static/certification.types";
|
|
14
|
+
import { DocumentTemplate } from "../documentation-templates";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Procedure represents a specific medical procedure that can be performed by a practitioner in a clinic
|
|
18
|
+
* It inherits properties from technology and adds clinic/practitioner specific details
|
|
19
|
+
*/
|
|
20
|
+
export interface Procedure {
|
|
21
|
+
/** Unique identifier of the procedure */
|
|
22
|
+
id: string;
|
|
23
|
+
/** Name of the procedure */
|
|
24
|
+
name: string;
|
|
25
|
+
/** Detailed description of the procedure */
|
|
26
|
+
description: string;
|
|
27
|
+
/** Family of procedures this belongs to (aesthetics/surgery) */
|
|
28
|
+
family: ProcedureFamily;
|
|
29
|
+
/** Category this procedure belongs to */
|
|
30
|
+
category: Category;
|
|
31
|
+
/** Subcategory this procedure belongs to */
|
|
32
|
+
subcategory: Subcategory;
|
|
33
|
+
/** Technology used in this procedure */
|
|
34
|
+
technology: Technology;
|
|
35
|
+
/** Product used in this procedure */
|
|
36
|
+
product: Product;
|
|
37
|
+
/** Price of the procedure */
|
|
38
|
+
price: number;
|
|
39
|
+
/** Currency for the price */
|
|
40
|
+
currency: Currency;
|
|
41
|
+
/** How the price is measured (per ml, per zone, etc.) */
|
|
42
|
+
pricingMeasure: PricingMeasure;
|
|
43
|
+
/** Duration of the procedure in minutes */
|
|
44
|
+
duration: number;
|
|
45
|
+
/** Blocking conditions that prevent this procedure */
|
|
46
|
+
blockingConditions: BlockingCondition[];
|
|
47
|
+
/** Treatment benefits of this procedure */
|
|
48
|
+
treatmentBenefits: TreatmentBenefit[];
|
|
49
|
+
/** Pre-procedure requirements */
|
|
50
|
+
preRequirements: Requirement[];
|
|
51
|
+
/** Post-procedure requirements */
|
|
52
|
+
postRequirements: Requirement[];
|
|
53
|
+
/** Certification requirements for performing this procedure */
|
|
54
|
+
certificationRequirement: CertificationRequirement;
|
|
55
|
+
/** Documentation templates required for this procedure */
|
|
56
|
+
documentationTemplates: DocumentTemplate[];
|
|
57
|
+
/** ID of the practitioner who performs this procedure */
|
|
58
|
+
practitionerId: string;
|
|
59
|
+
/** ID of the clinic branch where this procedure is performed */
|
|
60
|
+
clinicBranchId: string;
|
|
61
|
+
/** Whether this procedure is active */
|
|
62
|
+
isActive: boolean;
|
|
63
|
+
/** When this procedure was created */
|
|
64
|
+
createdAt: Date;
|
|
65
|
+
/** When this procedure was last updated */
|
|
66
|
+
updatedAt: Date;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Data required to create a new procedure
|
|
71
|
+
*/
|
|
72
|
+
export interface CreateProcedureData {
|
|
73
|
+
name: string;
|
|
74
|
+
description: string;
|
|
75
|
+
family: ProcedureFamily;
|
|
76
|
+
categoryId: string;
|
|
77
|
+
subcategoryId: string;
|
|
78
|
+
technologyId: string;
|
|
79
|
+
productId: string;
|
|
80
|
+
price: number;
|
|
81
|
+
currency: Currency;
|
|
82
|
+
pricingMeasure: PricingMeasure;
|
|
83
|
+
duration: number;
|
|
84
|
+
practitionerId: string;
|
|
85
|
+
clinicBranchId: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Data that can be updated for an existing procedure
|
|
90
|
+
*/
|
|
91
|
+
export interface UpdateProcedureData {
|
|
92
|
+
name?: string;
|
|
93
|
+
description?: string;
|
|
94
|
+
price?: number;
|
|
95
|
+
currency?: Currency;
|
|
96
|
+
pricingMeasure?: PricingMeasure;
|
|
97
|
+
duration?: number;
|
|
98
|
+
isActive?: boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Collection name for procedures in Firestore
|
|
103
|
+
*/
|
|
104
|
+
export const PROCEDURES_COLLECTION = "procedures";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ProcedureFamily } from "../backoffice/types/static/procedure-family.types";
|
|
3
|
+
import {
|
|
4
|
+
Currency,
|
|
5
|
+
PricingMeasure,
|
|
6
|
+
} from "../backoffice/types/static/pricing.types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Schema for creating a new procedure
|
|
10
|
+
*/
|
|
11
|
+
export const createProcedureSchema = z.object({
|
|
12
|
+
name: z.string().min(1).max(200),
|
|
13
|
+
description: z.string().min(1).max(2000),
|
|
14
|
+
family: z.nativeEnum(ProcedureFamily),
|
|
15
|
+
categoryId: z.string().min(1),
|
|
16
|
+
subcategoryId: z.string().min(1),
|
|
17
|
+
technologyId: z.string().min(1),
|
|
18
|
+
productId: z.string().min(1),
|
|
19
|
+
price: z.number().min(0),
|
|
20
|
+
currency: z.nativeEnum(Currency),
|
|
21
|
+
pricingMeasure: z.nativeEnum(PricingMeasure),
|
|
22
|
+
duration: z.number().min(1).max(480), // Max 8 hours
|
|
23
|
+
practitionerId: z.string().min(1),
|
|
24
|
+
clinicBranchId: z.string().min(1),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Schema for updating an existing procedure
|
|
29
|
+
*/
|
|
30
|
+
export const updateProcedureSchema = z.object({
|
|
31
|
+
name: z.string().min(1).max(200).optional(),
|
|
32
|
+
description: z.string().min(1).max(2000).optional(),
|
|
33
|
+
price: z.number().min(0).optional(),
|
|
34
|
+
currency: z.nativeEnum(Currency).optional(),
|
|
35
|
+
pricingMeasure: z.nativeEnum(PricingMeasure).optional(),
|
|
36
|
+
duration: z.number().min(1).max(480).optional(), // Max 8 hours
|
|
37
|
+
isActive: z.boolean().optional(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Schema for validating a complete procedure object
|
|
42
|
+
*/
|
|
43
|
+
export const procedureSchema = createProcedureSchema.extend({
|
|
44
|
+
id: z.string().min(1),
|
|
45
|
+
category: z.any(), // We'll validate the full category object separately
|
|
46
|
+
subcategory: z.any(), // We'll validate the full subcategory object separately
|
|
47
|
+
technology: z.any(), // We'll validate the full technology object separately
|
|
48
|
+
product: z.any(), // We'll validate the full product object separately
|
|
49
|
+
blockingConditions: z.array(z.any()), // We'll validate blocking conditions separately
|
|
50
|
+
treatmentBenefits: z.array(z.any()), // We'll validate treatment benefits separately
|
|
51
|
+
preRequirements: z.array(z.any()), // We'll validate requirements separately
|
|
52
|
+
postRequirements: z.array(z.any()), // We'll validate requirements separately
|
|
53
|
+
certificationRequirement: z.any(), // We'll validate certification requirement separately
|
|
54
|
+
documentationTemplates: z.array(z.any()), // We'll validate documentation templates separately
|
|
55
|
+
isActive: z.boolean(),
|
|
56
|
+
createdAt: z.date(),
|
|
57
|
+
updatedAt: z.date(),
|
|
58
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { UserRole } from "../types";
|
|
3
|
-
import { Timestamp } from "firebase/firestore";
|
|
3
|
+
import { Timestamp, FieldValue } from "firebase/firestore";
|
|
4
4
|
|
|
5
5
|
export const emailSchema = z
|
|
6
6
|
.string()
|
|
@@ -24,7 +24,13 @@ export const userRolesSchema = z
|
|
|
24
24
|
.min(1, "User must have at least one role")
|
|
25
25
|
.max(3, "User cannot have more than 3 roles");
|
|
26
26
|
|
|
27
|
-
export const timestampSchema = z.custom<Timestamp>((data) => {
|
|
27
|
+
export const timestampSchema = z.custom<Timestamp | FieldValue>((data) => {
|
|
28
|
+
// If it's a serverTimestamp (FieldValue), it's valid
|
|
29
|
+
if (data && typeof data === "object" && "isEqual" in data) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// If it's a Timestamp object, validate its structure
|
|
28
34
|
return (
|
|
29
35
|
data &&
|
|
30
36
|
typeof data === "object" &&
|
|
@@ -32,7 +38,7 @@ export const timestampSchema = z.custom<Timestamp>((data) => {
|
|
|
32
38
|
"seconds" in data &&
|
|
33
39
|
"nanoseconds" in data
|
|
34
40
|
);
|
|
35
|
-
}, "Must be a Timestamp object");
|
|
41
|
+
}, "Must be a Timestamp object or serverTimestamp");
|
|
36
42
|
|
|
37
43
|
/**
|
|
38
44
|
* Validaciona šema za clinic admin opcije pri kreiranju
|