@blackcode_sa/metaestetics-api 1.6.24 → 1.6.26

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/index.mjs CHANGED
@@ -8418,25 +8418,30 @@ var DocumentationTemplateService = class extends BaseService {
8418
8418
  * @returns The template or null if not found
8419
8419
  */
8420
8420
  async getTemplateById(templateId, version) {
8421
- if (version !== void 0) {
8422
- try {
8423
- const versionTemplate = await this.getTemplateVersion(
8424
- templateId,
8425
- version
8426
- );
8427
- if (versionTemplate) {
8428
- return versionTemplate;
8429
- }
8430
- } catch (error) {
8431
- console.error(`Error getting template version ${version}:`, error);
8432
- }
8433
- }
8434
8421
  const docRef = doc16(this.collectionRef, templateId);
8435
8422
  const docSnap = await getDoc19(docRef);
8436
8423
  if (!docSnap.exists()) {
8437
8424
  return null;
8438
8425
  }
8439
- return docSnap.data();
8426
+ const currentTemplate = docSnap.data();
8427
+ if (version === void 0) {
8428
+ return currentTemplate;
8429
+ }
8430
+ if (currentTemplate.version === version) {
8431
+ return currentTemplate;
8432
+ }
8433
+ try {
8434
+ const versionTemplate = await this.getTemplateVersion(
8435
+ templateId,
8436
+ version
8437
+ );
8438
+ if (versionTemplate) {
8439
+ return versionTemplate;
8440
+ }
8441
+ } catch (error) {
8442
+ console.error(`Error getting template version ${version}:`, error);
8443
+ }
8444
+ return null;
8440
8445
  }
8441
8446
  /**
8442
8447
  * Update an existing document template
@@ -8669,6 +8674,7 @@ var FilledDocumentService = class extends BaseService {
8669
8674
  /**
8670
8675
  * Create a new filled document within an appointment's subcollection.
8671
8676
  * @param templateId - ID of the template to use.
8677
+ * @param templateVersion - Version of the template to use.
8672
8678
  * @param appointmentId - ID of the appointment this form belongs to.
8673
8679
  * @param procedureId - ID of the procedure associated with this form.
8674
8680
  * @param patientId - ID of the patient.
@@ -8678,8 +8684,11 @@ var FilledDocumentService = class extends BaseService {
8678
8684
  * @param initialStatus - Optional initial status for the form.
8679
8685
  * @returns The created filled document.
8680
8686
  */
8681
- async createFilledDocumentForAppointment(templateId, appointmentId, procedureId, patientId, practitionerId, clinicId, initialValues = {}, initialStatus = "draft" /* DRAFT */) {
8682
- const template = await this.templateService.getTemplateById(templateId);
8687
+ async createFilledDocumentForAppointment(templateId, templateVersion, appointmentId, procedureId, patientId, practitionerId, clinicId, initialValues = {}, initialStatus = "draft" /* DRAFT */) {
8688
+ const template = await this.templateService.getTemplateById(
8689
+ templateId,
8690
+ templateVersion
8691
+ );
8683
8692
  if (!template) {
8684
8693
  throw new Error(`Template with ID ${templateId} not found`);
8685
8694
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.6.24",
4
+ "version": "1.6.26",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -44,6 +44,7 @@ import {
44
44
  USER_FORMS_SUBCOLLECTION,
45
45
  DOCTOR_FORMS_SUBCOLLECTION,
46
46
  } from "../../types/documentation-templates";
47
+ import { TechnologyDocumentationTemplate } from "../../backoffice/types/technology.types";
47
48
  import {
48
49
  ClinicInfo,
49
50
  PractitionerProfileInfo,
@@ -486,67 +487,49 @@ export class BookingAdmin {
486
487
  admin.firestore.Timestamp.now(),
487
488
  gender: patientSensitiveData?.gender || Gender.OTHER,
488
489
  };
489
- const procedureCategory = procedure.category as Category;
490
- const procedureSubCategory = procedure.subcategory as Subcategory;
491
- const procedureTechnology = procedure.technology as Technology;
492
- const procedureProduct = procedure.product as Product;
493
-
494
- const procedureInfo: ProcedureSummaryInfo = {
495
- id: procedure.id,
496
- name: procedure.name,
497
- description: procedure.description,
498
- family: procedure.family,
499
- categoryName: procedureCategory?.name || "",
500
- subcategoryName: procedureSubCategory?.name || "",
501
- technologyName: procedureTechnology?.name || "",
502
- price: procedure.price,
503
- pricingMeasure: procedure.pricingMeasure,
504
- currency: procedure.currency,
505
- duration: procedure.duration,
506
- clinicId: procedure.clinicBranchId,
507
- clinicName: clinicData.name,
508
- practitionerId: procedure.practitionerId,
509
- practitionerName: `${practitionerData.basicInfo.firstName} ${practitionerData.basicInfo.lastName}`,
510
- photo: procedure.photos?.[0] || "",
511
- brandName: procedureProduct?.brandName || "",
512
- productName: procedureProduct?.name || "",
513
- };
514
- const procedureExtendedInfo: ProcedureExtendedInfo = {
515
- id: procedure.id,
516
- name: procedure.name,
517
- description: procedure.description,
518
- cost: procedure.price,
519
- duration: procedure.duration,
520
- procedureFamily: procedure.family,
521
- procedureCategoryId: procedureCategory?.id || "",
522
- procedureCategoryName: procedureCategory?.name || "",
523
- procedureSubCategoryId: procedureSubCategory?.id || "",
524
- procedureSubCategoryName: procedureSubCategory?.name || "",
525
- procedureTechnologyId: procedureTechnology?.id || "",
526
- procedureTechnologyName: procedureTechnology?.name || "",
527
- procedureProductBrandId: procedureProduct.brandId || "",
528
- procedureProductBrandName: procedureProduct.brandName || "",
529
- procedureProductId: procedureProduct.id || "",
530
- procedureProductName: procedureProduct.name || "",
531
- };
532
-
533
- // --- 6. Determine pendingUserFormsIds and linkedFormIds ---
534
- let pendingUserFormsIds: string[] = [];
535
- let linkedFormIds: string[] = [];
536
- if (
537
- procedure.documentationTemplates &&
538
- Array.isArray(procedure.documentationTemplates)
539
- ) {
540
- pendingUserFormsIds = procedure.documentationTemplates
541
- .filter(
542
- (template: AppDocumentTemplate) =>
543
- template.isUserForm && template.isRequired
544
- )
545
- .map((template: AppDocumentTemplate) => template.id);
546
- linkedFormIds = procedure.documentationTemplates.map(
547
- (template: AppDocumentTemplate) => template.id
548
- );
549
- }
490
+ // const procedureCategory = procedure.category as Category;
491
+ // const procedureSubCategory = procedure.subcategory as Subcategory;
492
+ // const procedureTechnology = procedure.technology as Technology;
493
+ // const procedureProduct = procedure.product as Product;
494
+
495
+ // const procedureExtendedInfo: ProcedureExtendedInfo = {
496
+ // id: procedure.id,
497
+ // name: procedure.name,
498
+ // description: procedure.description,
499
+ // cost: procedure.price,
500
+ // duration: procedure.duration,
501
+ // procedureFamily: procedure.family,
502
+ // procedureCategoryId: procedureCategory?.id || "",
503
+ // procedureCategoryName: procedureCategory?.name || "",
504
+ // procedureSubCategoryId: procedureSubCategory?.id || "",
505
+ // procedureSubCategoryName: procedureSubCategory?.name || "",
506
+ // procedureTechnologyId: procedureTechnology?.id || "",
507
+ // procedureTechnologyName: procedureTechnology?.name || "",
508
+ // procedureProductBrandId: procedureProduct.brandId || "",
509
+ // procedureProductBrandName: procedureProduct.brandName || "",
510
+ // procedureProductId: procedureProduct.id || "",
511
+ // procedureProductName: procedureProduct.name || "",
512
+ // };
513
+
514
+ // // --- 6. Determine pendingUserFormsIds and linkedFormIds ---
515
+ // let pendingUserFormsIds: string[] = [];
516
+ // let linkedFormIds: string[] = [];
517
+ // if (
518
+ // procedure.documentationTemplates &&
519
+ // Array.isArray(procedure.documentationTemplates)
520
+ // ) {
521
+ // pendingUserFormsIds = procedure.documentationTemplates
522
+ // .filter(
523
+ // (template: TechnologyDocumentationTemplate) =>
524
+ // template.isUserForm && template.isRequired
525
+ // )
526
+ // .map(
527
+ // (template: TechnologyDocumentationTemplate) => template.templateId
528
+ // );
529
+ // linkedFormIds = procedure.documentationTemplates.map(
530
+ // (template: TechnologyDocumentationTemplate) => template.templateId
531
+ // );
532
+ // }
550
533
 
551
534
  // --- 7. Construct New Appointment Object ---
552
535
  const newAppointmentId = this.db
@@ -675,11 +658,11 @@ export class BookingAdmin {
675
658
  procedure.documentationTemplates.length > 0
676
659
  ) {
677
660
  const formInitResult =
678
- this.documentManagerAdmin.batchInitializeAppointmentForms(
661
+ await this.documentManagerAdmin.batchInitializeAppointmentFormsFromTechnologyTemplates(
679
662
  batch,
680
663
  newAppointmentId,
681
664
  procedure.id, // Pass the actual procedureId for the forms
682
- procedure.documentationTemplates as AppDocumentTemplate[],
665
+ procedure.documentationTemplates,
683
666
  data.patientId,
684
667
  procedure.practitionerId,
685
668
  procedure.clinicBranchId,
@@ -5,11 +5,13 @@ import {
5
5
  FilledDocumentStatus,
6
6
  USER_FORMS_SUBCOLLECTION,
7
7
  DOCTOR_FORMS_SUBCOLLECTION,
8
+ DOCUMENTATION_TEMPLATES_COLLECTION,
8
9
  } from "../../types/documentation-templates";
9
10
  import {
10
11
  APPOINTMENTS_COLLECTION,
11
12
  LinkedFormInfo,
12
13
  } from "../../types/appointment";
14
+ import { TechnologyDocumentationTemplate } from "../../backoffice/types/technology.types";
13
15
 
14
16
  export interface InitializeAppointmentFormsResult {
15
17
  initializedFormsInfo: LinkedFormInfo[];
@@ -128,4 +130,130 @@ export class DocumentManagerAdminService {
128
130
  }
129
131
  return { initializedFormsInfo, pendingUserFormsIds, allLinkedTemplateIds };
130
132
  }
133
+
134
+ /**
135
+ * Adds operations to a Firestore batch to initialize all linked forms for a new appointment
136
+ * using the TechnologyDocumentationTemplate references.
137
+ *
138
+ * @param dbBatch - The Firestore batch to add operations to.
139
+ * @param appointmentId - The ID of the newly created appointment.
140
+ * @param procedureIdForForms - The ID of the procedure associated with this appointment.
141
+ * @param technologyTemplates - Array of technology documentation template references.
142
+ * @param patientId - ID of the patient.
143
+ * @param practitionerId - ID of the practitioner associated with the procedure.
144
+ * @param clinicId - ID of the clinic where the procedure is performed.
145
+ * @param nowMillis - Current timestamp in milliseconds for createdAt/updatedAt.
146
+ * @returns An object containing initializedFormsInfo, pendingUserFormsIds, and allLinkedTemplateIds.
147
+ */
148
+ async batchInitializeAppointmentFormsFromTechnologyTemplates(
149
+ dbBatch: admin.firestore.WriteBatch,
150
+ appointmentId: string,
151
+ procedureIdForForms: string,
152
+ technologyTemplates: TechnologyDocumentationTemplate[],
153
+ patientId: string,
154
+ practitionerId: string,
155
+ clinicId: string,
156
+ nowMillis: number
157
+ ): Promise<InitializeAppointmentFormsResult> {
158
+ const initializedFormsInfo: LinkedFormInfo[] = [];
159
+ const pendingUserFormsIds: string[] = [];
160
+ const allLinkedTemplateIds: string[] = [];
161
+
162
+ if (!technologyTemplates || technologyTemplates.length === 0) {
163
+ console.log(
164
+ `[DocManagerAdmin] No document templates to initialize for appointment ${appointmentId}.`
165
+ );
166
+ return {
167
+ initializedFormsInfo,
168
+ pendingUserFormsIds,
169
+ allLinkedTemplateIds,
170
+ };
171
+ }
172
+
173
+ // Fetch all template documents in one batch
174
+ const templateIds = technologyTemplates.map((t) => t.templateId);
175
+ const templatesSnapshot = await this.db
176
+ .collection(DOCUMENTATION_TEMPLATES_COLLECTION)
177
+ .where(admin.firestore.FieldPath.documentId(), "in", templateIds)
178
+ .get();
179
+
180
+ const templatesMap = new Map<string, DocumentTemplate>();
181
+ templatesSnapshot.forEach((doc) => {
182
+ templatesMap.set(doc.id, doc.data() as DocumentTemplate);
183
+ });
184
+
185
+ for (const templateRef of technologyTemplates) {
186
+ const template = templatesMap.get(templateRef.templateId);
187
+ if (!template) {
188
+ console.warn(
189
+ `[DocManagerAdmin] Template ${templateRef.templateId} not found in Firestore.`
190
+ );
191
+ continue;
192
+ }
193
+
194
+ const isUserForm = templateRef.isUserForm;
195
+ const isRequired = templateRef.isRequired;
196
+
197
+ const formSubcollectionPath = isUserForm
198
+ ? USER_FORMS_SUBCOLLECTION
199
+ : DOCTOR_FORMS_SUBCOLLECTION;
200
+
201
+ const filledDocumentId = this.db
202
+ .collection(APPOINTMENTS_COLLECTION)
203
+ .doc(appointmentId)
204
+ .collection(formSubcollectionPath)
205
+ .doc().id;
206
+
207
+ if (isUserForm && isRequired) {
208
+ pendingUserFormsIds.push(filledDocumentId);
209
+ }
210
+
211
+ allLinkedTemplateIds.push(filledDocumentId);
212
+ // Always initialize with PENDING status regardless of whether the form is required
213
+ // PENDING is the starting state, DRAFT is when a form is saved but not submitted
214
+ const initialStatus = FilledDocumentStatus.PENDING;
215
+
216
+ const filledDocumentData: FilledDocument = {
217
+ id: filledDocumentId,
218
+ templateId: templateRef.templateId,
219
+ templateVersion: template.version,
220
+ isUserForm: isUserForm,
221
+ isRequired: isRequired,
222
+ appointmentId: appointmentId,
223
+ procedureId: procedureIdForForms,
224
+ patientId: patientId,
225
+ practitionerId: practitionerId,
226
+ clinicId: clinicId,
227
+ createdAt: nowMillis,
228
+ updatedAt: nowMillis,
229
+ values: {},
230
+ status: initialStatus,
231
+ };
232
+
233
+ const docRef = this.db
234
+ .collection(APPOINTMENTS_COLLECTION)
235
+ .doc(appointmentId)
236
+ .collection(formSubcollectionPath)
237
+ .doc(filledDocumentId);
238
+
239
+ dbBatch.set(docRef, filledDocumentData);
240
+
241
+ const linkedForm: LinkedFormInfo = {
242
+ formId: filledDocumentData.id,
243
+ templateId: template.id,
244
+ templateVersion: template.version,
245
+ title: template.title,
246
+ isUserForm: filledDocumentData.isUserForm,
247
+ isRequired: filledDocumentData.isRequired,
248
+ status: filledDocumentData.status,
249
+ path: docRef.path,
250
+ };
251
+ initializedFormsInfo.push(linkedForm);
252
+
253
+ console.log(
254
+ `[DocManagerAdmin] Added FilledDocument ${filledDocumentId} (template: ${template.id}) and its LinkedFormInfo to batch for appointment ${appointmentId}.`
255
+ );
256
+ }
257
+ return { initializedFormsInfo, pendingUserFormsIds, allLinkedTemplateIds };
258
+ }
131
259
  }
@@ -6,6 +6,20 @@ import { CertificationRequirement } from "./static/certification.types";
6
6
  import { DocumentTemplate } from "../../types/documentation-templates";
7
7
  import { ProcedureFamily } from "./static/procedure-family.types";
8
8
 
9
+ /**
10
+ * Reference to a documentation template with metadata
11
+ * @property templateId - ID of the documentation template
12
+ * @property isUserForm - Whether this template is filled by users
13
+ * @property isRequired - Whether this template is required
14
+ * @property sortingOrder - The display order of this template
15
+ */
16
+ export interface TechnologyDocumentationTemplate {
17
+ templateId: string;
18
+ isUserForm: boolean;
19
+ isRequired: boolean;
20
+ sortingOrder: number;
21
+ }
22
+
9
23
  /**
10
24
  * Zahtevi koji su povezani sa tehnologijom
11
25
  * @property pre - Lista zahteva koji se moraju ispuniti pre procedure
@@ -33,7 +47,7 @@ export interface TechnologyRequirements {
33
47
  * @property contraindications - List of conditions requiring special attention
34
48
  * @property benefits - List of expected benefits from the procedure
35
49
  * @property certificationRequirement - Required certification level and specialties
36
- * @property documentationTemplates - List of documentation templates required for this technology
50
+ * @property documentationTemplates - List of documentation template references
37
51
  * @property isActive - Whether the technology is active in the system
38
52
  * @property createdAt - Creation date
39
53
  * @property updatedAt - Last update date
@@ -54,7 +68,7 @@ export interface Technology {
54
68
  contraindications: Contraindication[];
55
69
  benefits: TreatmentBenefit[];
56
70
  certificationRequirement: CertificationRequirement;
57
- documentationTemplates?: DocumentTemplate[];
71
+ documentationTemplates?: TechnologyDocumentationTemplate[];
58
72
  isActive: boolean;
59
73
  createdAt: Date;
60
74
  updatedAt: Date;
@@ -95,33 +95,49 @@ export class DocumentationTemplateService extends BaseService {
95
95
  templateId: string,
96
96
  version?: number
97
97
  ): Promise<DocumentTemplate | null> {
98
- // If version is specified, try to get that specific version
99
- if (version !== undefined) {
100
- try {
101
- const versionTemplate = await this.getTemplateVersion(
102
- templateId,
103
- version
104
- );
105
- // If the version exists, return it
106
- if (versionTemplate) {
107
- return versionTemplate;
108
- }
109
- // If version doesn't exist, fall back to latest version (continue with code below)
110
- } catch (error) {
111
- console.error(`Error getting template version ${version}:`, error);
112
- // Fall back to latest version if error occurs
113
- }
114
- }
115
-
116
- // Get the latest version (default behavior)
98
+ // First, check if the template exists at all
117
99
  const docRef = doc(this.collectionRef, templateId);
118
100
  const docSnap = await getDoc(docRef);
119
101
 
120
102
  if (!docSnap.exists()) {
121
- return null;
103
+ return null; // Template doesn't exist
104
+ }
105
+
106
+ const currentTemplate = docSnap.data() as DocumentTemplate;
107
+
108
+ // If no specific version is requested, simply return the current template
109
+ if (version === undefined) {
110
+ return currentTemplate;
122
111
  }
123
112
 
124
- return docSnap.data() as DocumentTemplate;
113
+ // If the requested version matches the current version, return it
114
+ if (currentTemplate.version === version) {
115
+ return currentTemplate;
116
+ }
117
+
118
+ // Otherwise, try to find the requested version in the versions subcollection
119
+ try {
120
+ const versionTemplate = await this.getTemplateVersion(
121
+ templateId,
122
+ version
123
+ );
124
+ if (versionTemplate) {
125
+ return versionTemplate;
126
+ }
127
+ } catch (error) {
128
+ console.error(`Error getting template version ${version}:`, error);
129
+ }
130
+
131
+ // If we get here, the requested version doesn't exist
132
+ // Option 1: Return null (strict approach)
133
+ return null;
134
+
135
+ // Option 2: Return current version but indicate it's not the requested version (uncomment to use)
136
+ // return {
137
+ // ...currentTemplate,
138
+ // _versionRequested: version,
139
+ // _versionNotFound: true
140
+ // } as DocumentTemplate;
125
141
  }
126
142
 
127
143
  /**
@@ -48,6 +48,7 @@ export class FilledDocumentService extends BaseService {
48
48
  /**
49
49
  * Create a new filled document within an appointment's subcollection.
50
50
  * @param templateId - ID of the template to use.
51
+ * @param templateVersion - Version of the template to use.
51
52
  * @param appointmentId - ID of the appointment this form belongs to.
52
53
  * @param procedureId - ID of the procedure associated with this form.
53
54
  * @param patientId - ID of the patient.
@@ -59,6 +60,7 @@ export class FilledDocumentService extends BaseService {
59
60
  */
60
61
  async createFilledDocumentForAppointment(
61
62
  templateId: string,
63
+ templateVersion: number,
62
64
  appointmentId: string,
63
65
  procedureId: string,
64
66
  patientId: string,
@@ -67,7 +69,10 @@ export class FilledDocumentService extends BaseService {
67
69
  initialValues: { [elementId: string]: any } = {},
68
70
  initialStatus: FilledDocumentStatus = FilledDocumentStatus.DRAFT
69
71
  ): Promise<FilledDocument> {
70
- const template = await this.templateService.getTemplateById(templateId);
72
+ const template = await this.templateService.getTemplateById(
73
+ templateId,
74
+ templateVersion
75
+ );
71
76
  if (!template) {
72
77
  throw new Error(`Template with ID ${templateId} not found`);
73
78
  }
@@ -1,7 +1,10 @@
1
1
  import { ProcedureFamily } from "../../backoffice/types/static/procedure-family.types";
2
2
  import { Category } from "../../backoffice/types/category.types";
3
3
  import { Subcategory } from "../../backoffice/types/subcategory.types";
4
- import { Technology } from "../../backoffice/types/technology.types";
4
+ import {
5
+ Technology,
6
+ type TechnologyDocumentationTemplate,
7
+ } from "../../backoffice/types/technology.types";
5
8
  import { Product } from "../../backoffice/types/product.types";
6
9
  import {
7
10
  PricingMeasure,
@@ -62,7 +65,7 @@ export interface Procedure {
62
65
  /** Certification requirements for performing this procedure */
63
66
  certificationRequirement: CertificationRequirement;
64
67
  /** Documentation templates required for this procedure */
65
- documentationTemplates: DocumentTemplate[];
68
+ documentationTemplates: TechnologyDocumentationTemplate[];
66
69
  /** ID of the practitioner who performs this procedure */
67
70
  practitionerId: string;
68
71
  /** ID of the clinic branch where this procedure is performed */