@blackcode_sa/metaestetics-api 1.7.16 → 1.7.18

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.
@@ -19,11 +19,17 @@ import {
19
19
  // FILLED_DOCUMENTS_COLLECTION, // This will be replaced by subcollection paths
20
20
  FilledDocument,
21
21
  FilledDocumentStatus,
22
+ FilledDocumentFileValue,
22
23
  USER_FORMS_SUBCOLLECTION,
23
24
  DOCTOR_FORMS_SUBCOLLECTION,
24
25
  } from "../../types"; // General types
25
26
  import { APPOINTMENTS_COLLECTION } from "../../types/appointment"; // Specific import for the constant
26
27
  import { DocumentationTemplateService } from "./documentation-template.service";
28
+ import {
29
+ MediaService,
30
+ MediaAccessLevel,
31
+ MediaMetadata,
32
+ } from "../media/media.service";
27
33
  // Import the new validation schemas if you plan to use them for input validation here
28
34
  // import { createFilledDocumentDataSchema, updateFilledDocumentDataSchema } from '../../validations/documentation-templates.schema';
29
35
 
@@ -33,10 +39,12 @@ import { DocumentationTemplateService } from "./documentation-template.service";
33
39
  export class FilledDocumentService extends BaseService {
34
40
  // No single collectionRef anymore, paths will be dynamic
35
41
  private readonly templateService: DocumentationTemplateService;
42
+ private readonly mediaService: MediaService;
36
43
 
37
44
  constructor(...args: ConstructorParameters<typeof BaseService>) {
38
45
  super(...args);
39
46
  this.templateService = new DocumentationTemplateService(...args); // Pass db and other args
47
+ this.mediaService = new MediaService(...args); // Initialize media service with the same args
40
48
  }
41
49
 
42
50
  private getFormSubcollectionPath(
@@ -468,4 +476,216 @@ export class FilledDocumentService extends BaseService {
468
476
  */
469
477
  return { documents: [], lastDoc: null }; // Placeholder return
470
478
  }
479
+
480
+ /**
481
+ * Upload a file and associate it with a filled document field.
482
+ * @param appointmentId - ID of the appointment.
483
+ * @param formId - ID of the filled document.
484
+ * @param isUserForm - Boolean indicating if it's a user form or doctor form.
485
+ * @param file - The file to upload.
486
+ * @param fieldId - The ID of the field in the document to associate with this file.
487
+ * @param accessLevel - Access level for the file, defaults to PRIVATE.
488
+ * @returns The updated filled document with file information.
489
+ */
490
+ async uploadFileForFilledDocument(
491
+ appointmentId: string,
492
+ formId: string,
493
+ isUserForm: boolean,
494
+ file: File | Blob,
495
+ fieldId: string,
496
+ accessLevel: MediaAccessLevel = MediaAccessLevel.PRIVATE
497
+ ): Promise<FilledDocument> {
498
+ console.log(
499
+ `[FilledDocumentService] Uploading file for field ${fieldId} in form ${formId}`
500
+ );
501
+
502
+ // 1. Get the existing document to verify it exists and to get the patientId
503
+ const existingDoc = await this.getFilledDocumentFromAppointmentById(
504
+ appointmentId,
505
+ formId,
506
+ isUserForm
507
+ );
508
+
509
+ if (!existingDoc) {
510
+ throw new Error(
511
+ `Filled document with ID ${formId} not found in appointment ${appointmentId}`
512
+ );
513
+ }
514
+
515
+ // 2. Upload the file using MediaService
516
+ const ownerId = existingDoc.patientId; // Using patientId as ownerId for permissions
517
+ const collectionName = isUserForm
518
+ ? "patient_forms_files"
519
+ : "doctor_forms_files";
520
+
521
+ const mediaMetadata = await this.mediaService.uploadMedia(
522
+ file,
523
+ ownerId,
524
+ accessLevel,
525
+ collectionName,
526
+ file instanceof File ? file.name : `${fieldId}_file`
527
+ );
528
+
529
+ // 3. Create a file value object to store in the document
530
+ const fileValue: FilledDocumentFileValue = {
531
+ mediaId: mediaMetadata.id,
532
+ url: mediaMetadata.url,
533
+ name: mediaMetadata.name,
534
+ contentType: mediaMetadata.contentType,
535
+ size: mediaMetadata.size,
536
+ uploadedAt: Date.now(),
537
+ };
538
+
539
+ // 4. Update the document with the file value
540
+ const values = {
541
+ [fieldId]: fileValue,
542
+ };
543
+
544
+ // 5. Use the existing update method to save the file reference
545
+ return this.updateFilledDocumentInAppointment(
546
+ appointmentId,
547
+ formId,
548
+ isUserForm,
549
+ values
550
+ );
551
+ }
552
+
553
+ /**
554
+ * Upload a signature image for a filled document field.
555
+ * This is a specialized version of uploadFileForFilledDocument specifically for signatures.
556
+ * @param appointmentId - ID of the appointment.
557
+ * @param formId - ID of the filled document.
558
+ * @param isUserForm - Boolean indicating if it's a user form or doctor form.
559
+ * @param signatureBlob - The signature image as a Blob.
560
+ * @param fieldId - The ID of the signature field in the document.
561
+ * @returns The updated filled document with signature information.
562
+ */
563
+ async uploadSignatureForFilledDocument(
564
+ appointmentId: string,
565
+ formId: string,
566
+ isUserForm: boolean,
567
+ signatureBlob: Blob,
568
+ fieldId: string
569
+ ): Promise<FilledDocument> {
570
+ console.log(
571
+ `[FilledDocumentService] Uploading signature for field ${fieldId} in form ${formId}`
572
+ );
573
+
574
+ // Use the general file upload method, but specify a fixed name and access level for signatures
575
+ const signatureFile = new File(
576
+ [signatureBlob],
577
+ `signature_${fieldId}.png`,
578
+ {
579
+ type: "image/png",
580
+ }
581
+ );
582
+
583
+ return this.uploadFileForFilledDocument(
584
+ appointmentId,
585
+ formId,
586
+ isUserForm,
587
+ signatureFile,
588
+ fieldId,
589
+ MediaAccessLevel.CONFIDENTIAL // Signatures should be confidential
590
+ );
591
+ }
592
+
593
+ /**
594
+ * Remove a file from a filled document field.
595
+ * This will both update the document and delete the media file.
596
+ * @param appointmentId - ID of the appointment.
597
+ * @param formId - ID of the filled document.
598
+ * @param isUserForm - Boolean indicating if it's a user form or doctor form.
599
+ * @param fieldId - The ID of the field containing the file.
600
+ * @returns The updated filled document with the file removed.
601
+ */
602
+ async removeFileFromFilledDocument(
603
+ appointmentId: string,
604
+ formId: string,
605
+ isUserForm: boolean,
606
+ fieldId: string
607
+ ): Promise<FilledDocument> {
608
+ console.log(
609
+ `[FilledDocumentService] Removing file from field ${fieldId} in form ${formId}`
610
+ );
611
+
612
+ // 1. Get the existing document to verify it exists and to get the file info
613
+ const existingDoc = await this.getFilledDocumentFromAppointmentById(
614
+ appointmentId,
615
+ formId,
616
+ isUserForm
617
+ );
618
+
619
+ if (!existingDoc) {
620
+ throw new Error(
621
+ `Filled document with ID ${formId} not found in appointment ${appointmentId}`
622
+ );
623
+ }
624
+
625
+ // 2. Check if the field has a file value
626
+ const fileValue = existingDoc.values?.[fieldId] as FilledDocumentFileValue;
627
+ if (fileValue && fileValue.mediaId) {
628
+ // 3. Delete the file using MediaService
629
+ try {
630
+ await this.mediaService.deleteMedia(fileValue.mediaId);
631
+ } catch (error) {
632
+ console.error(
633
+ `[FilledDocumentService] Error deleting media ${fileValue.mediaId}:`,
634
+ error
635
+ );
636
+ // Continue with document update even if media deletion fails
637
+ }
638
+ }
639
+
640
+ // 4. Update the document to remove the file reference
641
+ const values = {
642
+ [fieldId]: null,
643
+ };
644
+
645
+ // 5. Use the existing update method to save the changes
646
+ return this.updateFilledDocumentInAppointment(
647
+ appointmentId,
648
+ formId,
649
+ isUserForm,
650
+ values
651
+ );
652
+ }
653
+
654
+ /**
655
+ * Get the download URL for a file in a filled document.
656
+ * @param appointmentId - ID of the appointment.
657
+ * @param formId - ID of the filled document.
658
+ * @param isUserForm - Boolean indicating if it's a user form or doctor form.
659
+ * @param fieldId - The ID of the field containing the file.
660
+ * @returns The download URL for the file, or null if not found.
661
+ */
662
+ async getFileUrlFromFilledDocument(
663
+ appointmentId: string,
664
+ formId: string,
665
+ isUserForm: boolean,
666
+ fieldId: string
667
+ ): Promise<string | null> {
668
+ console.log(
669
+ `[FilledDocumentService] Getting file URL for field ${fieldId} in form ${formId}`
670
+ );
671
+
672
+ // 1. Get the document
673
+ const doc = await this.getFilledDocumentFromAppointmentById(
674
+ appointmentId,
675
+ formId,
676
+ isUserForm
677
+ );
678
+
679
+ if (!doc) {
680
+ return null;
681
+ }
682
+
683
+ // 2. Check if the field has a file value
684
+ const fileValue = doc.values?.[fieldId] as FilledDocumentFileValue;
685
+ if (fileValue && fileValue.url) {
686
+ return fileValue.url;
687
+ }
688
+
689
+ return null;
690
+ }
471
691
  }
@@ -279,7 +279,7 @@ export interface FilledDocument {
279
279
  clinicId: string;
280
280
  createdAt: number;
281
281
  updatedAt: number;
282
- values: { [elementId: string]: any }; // Values for each element
282
+ values: { [elementId: string]: any | FilledDocumentFileValue }; // Values for each element
283
283
  status: FilledDocumentStatus;
284
284
  }
285
285
 
@@ -294,3 +294,15 @@ export enum FilledDocumentStatus {
294
294
  SIGNED = "signed", // Only used for user forms
295
295
  REJECTED = "rejected", // Only used for user forms
296
296
  }
297
+
298
+ /**
299
+ * Interface for file upload results to be stored in filled document values
300
+ */
301
+ export interface FilledDocumentFileValue {
302
+ mediaId: string;
303
+ url: string;
304
+ name: string;
305
+ contentType: string;
306
+ size: number;
307
+ uploadedAt: number;
308
+ }