diff --git a/apps/mobile/packages/data_connect/lib/src/connectors/staff/data/repositories/staff_connector_repository_impl.dart b/apps/mobile/packages/data_connect/lib/src/connectors/staff/data/repositories/staff_connector_repository_impl.dart index 24f01a00..57047403 100644 --- a/apps/mobile/packages/data_connect/lib/src/connectors/staff/data/repositories/staff_connector_repository_impl.dart +++ b/apps/mobile/packages/data_connect/lib/src/connectors/staff/data/repositories/staff_connector_repository_impl.dart @@ -1,4 +1,5 @@ import 'package:firebase_data_connect/firebase_data_connect.dart'; +import 'package:krow_core/core.dart'; import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_domain/krow_domain.dart' as domain; import '../../domain/repositories/staff_connector_repository.dart'; @@ -349,4 +350,178 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository { throw Exception('Error signing out: ${e.toString()}'); } } + + @override + Future> getStaffDocuments() async { + return _service.run(() async { + final String staffId = await _service.getStaffId(); + + final List> results = + await Future.wait>( + >>[ + _service.connector.listDocuments().execute(), + _service.connector + .listStaffDocumentsByStaffId(staffId: staffId) + .execute(), + ], + ); + + final QueryResult documentsRes = + results[0] as QueryResult; + final QueryResult< + dc.ListStaffDocumentsByStaffIdData, + dc.ListStaffDocumentsByStaffIdVariables + > + staffDocsRes = + results[1] + as QueryResult< + dc.ListStaffDocumentsByStaffIdData, + dc.ListStaffDocumentsByStaffIdVariables + >; + + final List staffDocs = + staffDocsRes.data.staffDocuments; + + return documentsRes.data.documents.map((dc.ListDocumentsDocuments doc) { + // Find if this staff member has already uploaded this document + final dc.ListStaffDocumentsByStaffIdStaffDocuments? currentDoc = + staffDocs + .where( + (dc.ListStaffDocumentsByStaffIdStaffDocuments d) => + d.documentId == doc.id, + ) + .firstOrNull; + + return domain.StaffDocument( + id: currentDoc?.id ?? '', + staffId: staffId, + documentId: doc.id, + name: doc.name, + description: doc.description, + status: currentDoc != null + ? _mapDocumentStatus(currentDoc.status) + : domain.DocumentStatus.missing, + documentUrl: currentDoc?.documentUrl, + expiryDate: currentDoc?.expiryDate == null + ? null + : DateTimeUtils.toDeviceTime( + currentDoc!.expiryDate!.toDateTime(), + ), + verificationId: currentDoc?.verificationId, + verificationStatus: currentDoc != null + ? _mapFromDCDocumentVerificationStatus(currentDoc.status) + : null, + ); + }).toList(); + }); + } + + @override + Future upsertStaffDocument({ + required String documentId, + required String documentUrl, + domain.DocumentStatus? status, + String? verificationId, + }) async { + await _service.run(() async { + final String staffId = await _service.getStaffId(); + final domain.Staff staff = await getStaffProfile(); + + await _service.connector + .upsertStaffDocument( + staffId: staffId, + staffName: staff.name, + documentId: documentId, + status: _mapToDCDocumentStatus(status), + ) + .documentUrl(documentUrl) + .verificationId(verificationId) + .execute(); + }); + } + + domain.DocumentStatus _mapDocumentStatus( + dc.EnumValue status, + ) { + if (status is dc.Unknown) { + return domain.DocumentStatus.pending; + } + final dc.DocumentStatus value = + (status as dc.Known).value; + switch (value) { + case dc.DocumentStatus.VERIFIED: + return domain.DocumentStatus.verified; + case dc.DocumentStatus.PENDING: + return domain.DocumentStatus.pending; + case dc.DocumentStatus.MISSING: + return domain.DocumentStatus.missing; + case dc.DocumentStatus.UPLOADED: + case dc.DocumentStatus.EXPIRING: + return domain.DocumentStatus.pending; + case dc.DocumentStatus.PROCESSING: + case dc.DocumentStatus.AUTO_PASS: + case dc.DocumentStatus.AUTO_FAIL: + case dc.DocumentStatus.NEEDS_REVIEW: + case dc.DocumentStatus.APPROVED: + case dc.DocumentStatus.REJECTED: + case dc.DocumentStatus.ERROR: + if (value == dc.DocumentStatus.AUTO_PASS || + value == dc.DocumentStatus.APPROVED) { + return domain.DocumentStatus.verified; + } + if (value == dc.DocumentStatus.AUTO_FAIL || + value == dc.DocumentStatus.REJECTED || + value == dc.DocumentStatus.ERROR) { + return domain.DocumentStatus.rejected; + } + return domain.DocumentStatus.pending; + } + } + + domain.DocumentVerificationStatus _mapFromDCDocumentVerificationStatus( + dc.EnumValue status, + ) { + if (status is dc.Unknown) { + return domain.DocumentVerificationStatus.error; + } + final String name = (status as dc.Known).value.name; + switch (name) { + case 'PENDING': + return domain.DocumentVerificationStatus.pending; + case 'PROCESSING': + return domain.DocumentVerificationStatus.processing; + case 'AUTO_PASS': + return domain.DocumentVerificationStatus.autoPass; + case 'AUTO_FAIL': + return domain.DocumentVerificationStatus.autoFail; + case 'NEEDS_REVIEW': + return domain.DocumentVerificationStatus.needsReview; + case 'APPROVED': + return domain.DocumentVerificationStatus.approved; + case 'REJECTED': + return domain.DocumentVerificationStatus.rejected; + case 'VERIFIED': + return domain.DocumentVerificationStatus.approved; + case 'ERROR': + return domain.DocumentVerificationStatus.error; + default: + return domain.DocumentVerificationStatus.error; + } + } + + dc.DocumentStatus _mapToDCDocumentStatus(domain.DocumentStatus? status) { + if (status == null) return dc.DocumentStatus.PENDING; + switch (status) { + case domain.DocumentStatus.verified: + return dc.DocumentStatus.VERIFIED; + case domain.DocumentStatus.pending: + return dc.DocumentStatus.PENDING; + case domain.DocumentStatus.missing: + return dc.DocumentStatus.MISSING; + case domain.DocumentStatus.rejected: + return dc.DocumentStatus.REJECTED; + case domain.DocumentStatus.expired: + return dc.DocumentStatus.EXPIRING; + } + } } diff --git a/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/repositories/staff_connector_repository.dart b/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/repositories/staff_connector_repository.dart index 3bd3c9e7..6e60cb22 100644 --- a/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/repositories/staff_connector_repository.dart +++ b/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/repositories/staff_connector_repository.dart @@ -72,4 +72,15 @@ abstract interface class StaffConnectorRepository { String? bio, String? profilePictureUrl, }); + + /// Fetches the staff documents for the current authenticated user. + Future> getStaffDocuments(); + + /// Upserts staff document information. + Future upsertStaffDocument({ + required String documentId, + required String documentUrl, + DocumentStatus? status, + String? verificationId, + }); } diff --git a/apps/mobile/packages/domain/lib/krow_domain.dart b/apps/mobile/packages/domain/lib/krow_domain.dart index 87167b9e..f78390ce 100644 --- a/apps/mobile/packages/domain/lib/krow_domain.dart +++ b/apps/mobile/packages/domain/lib/krow_domain.dart @@ -77,6 +77,7 @@ export 'src/adapters/financial/bank_account/bank_account_adapter.dart'; // Profile export 'src/entities/profile/staff_document.dart'; +export 'src/entities/profile/document_verification_status.dart'; export 'src/entities/profile/attire_item.dart'; export 'src/entities/profile/attire_verification_status.dart'; export 'src/entities/profile/relationship_type.dart'; diff --git a/apps/mobile/packages/domain/lib/src/entities/profile/document_verification_status.dart b/apps/mobile/packages/domain/lib/src/entities/profile/document_verification_status.dart new file mode 100644 index 00000000..99b47bb8 --- /dev/null +++ b/apps/mobile/packages/domain/lib/src/entities/profile/document_verification_status.dart @@ -0,0 +1,39 @@ +/// Represents the verification status of a compliance document. +enum DocumentVerificationStatus { + /// Job is created and waiting to be processed. + pending('PENDING'), + + /// Job is currently being processed by machine or human. + processing('PROCESSING'), + + /// Machine verification passed automatically. + autoPass('AUTO_PASS'), + + /// Machine verification failed automatically. + autoFail('AUTO_FAIL'), + + /// Machine results are inconclusive and require human review. + needsReview('NEEDS_REVIEW'), + + /// Human reviewer approved the verification. + approved('APPROVED'), + + /// Human reviewer rejected the verification. + rejected('REJECTED'), + + /// An error occurred during processing. + error('ERROR'); + + const DocumentVerificationStatus(this.value); + + /// The string value expected by the Core API. + final String value; + + /// Creates a [DocumentVerificationStatus] from a string. + static DocumentVerificationStatus fromString(String value) { + return DocumentVerificationStatus.values.firstWhere( + (DocumentVerificationStatus e) => e.value == value, + orElse: () => DocumentVerificationStatus.error, + ); + } +} diff --git a/apps/mobile/packages/domain/lib/src/entities/profile/staff_document.dart b/apps/mobile/packages/domain/lib/src/entities/profile/staff_document.dart index 01305436..d8dd5958 100644 --- a/apps/mobile/packages/domain/lib/src/entities/profile/staff_document.dart +++ b/apps/mobile/packages/domain/lib/src/entities/profile/staff_document.dart @@ -1,17 +1,12 @@ import 'package:equatable/equatable.dart'; +import 'document_verification_status.dart'; + /// Status of a compliance document. -enum DocumentStatus { - verified, - pending, - missing, - rejected, - expired -} +enum DocumentStatus { verified, pending, missing, rejected, expired } /// Represents a staff compliance document. class StaffDocument extends Equatable { - const StaffDocument({ required this.id, required this.staffId, @@ -21,7 +16,10 @@ class StaffDocument extends Equatable { required this.status, this.documentUrl, this.expiryDate, + this.verificationId, + this.verificationStatus, }); + /// The unique identifier of the staff document record. final String id; @@ -46,15 +44,23 @@ class StaffDocument extends Equatable { /// The expiry date of the document. final DateTime? expiryDate; + /// The ID of the verification record. + final String? verificationId; + + /// The detailed verification status. + final DocumentVerificationStatus? verificationStatus; + @override List get props => [ - id, - staffId, - documentId, - name, - description, - status, - documentUrl, - expiryDate, - ]; + id, + staffId, + documentId, + name, + description, + status, + documentUrl, + expiryDate, + verificationId, + verificationStatus, + ]; } diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/IMPLEMENTATION_WORKFLOW.md b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/IMPLEMENTATION_WORKFLOW.md index 2c71be01..2fdddf14 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/IMPLEMENTATION_WORKFLOW.md +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/IMPLEMENTATION_WORKFLOW.md @@ -87,75 +87,51 @@ To ensure a consistent experience across all compliance uploads (documents, cert --- -## 4. Pending — Data Layer (Real Repository Implementation) - -> The `DocumentsRepositoryImpl.uploadDocument` method is currently a **mock**. Replace it with the following sequence: - -### Step-by-step Repository Implementation - -```dart -@override -Future uploadDocument( - String documentId, - String filePath, -) async { - return _service.execute(() async { - // 1. Upload file to cloud storage - final String fileUri = await _fileUploadService.upload(filePath); - - // 2. Generate a signed URL for viewing - final String documentUrl = - await _signedUrlService.createSignedUrl(fileUri); - - // 3. Initiate verification job - final String verificationId = await _verificationService - .createVerification(fileUri: fileUri, documentType: documentId); - - // 4. (Optional) Poll for immediate feedback (~10s) - // final VerificationStatus status = - // await _verificationService.poll(verificationId, maxSeconds: 10); - - // 5. Persist to Data Connect - final String staffId = await _service.getStaffId(); - await _service.connector.upsertStaffDocument( - staffId: staffId, - documentId: documentId, - documentUrl: documentUrl, - verificationId: verificationId, - status: DocumentStatus.UPLOADED, - ); - - return domain.StaffDocument( - id: documentId, - staffId: staffId, - documentId: documentId, - name: '', // populated from local cache or refetch - status: domain.DocumentStatus.pending, - documentUrl: documentUrl, - ); - }); -} -``` +### ✅ Completed — Data Layer + + #### Data Connect Integration + - `StaffConnectorRepository` interface updated with `getStaffDocuments()` and `upsertStaffDocument()`. + - `upsertStaffDocument` mutation in `backend/dataconnect/connector/staffDocument/mutations.gql` updated to accept `verificationId`. + - `getStaffDocumentByKey` and `listStaffDocumentsByStaffId` queries updated to include `verificationId`. + - SDK regenerated: `make dataconnect-generate-sdk ENV=dev`. + + #### Repository Implementation — `StaffConnectorRepositoryImpl` + - **`getStaffDocuments()`**: + - Uses `Future.wait` to simultaneously fetch the full list of available `Document` types and the current staff's `StaffDocument` records. + - Maps the master list of `Document` entities, joining them with any existing `StaffDocument` entry. + - This ensures the UI always shows all required documents, even if they haven't been uploaded yet (status: `missing`). + - Populates `verificationId` and `verificationStatus` for presentation layer mapping. + - **`upsertStaffDocument()`**: + - Handles the upsert (create or update) of a staff document record. + - Explicitly passes `documentUrl` and `verificationId`, ensuring metadata is persisted alongside the file reference. + - **Status Mapping**: + - `_mapDocumentStatus`: Collapses backend statuses into domain `verified | pending | rejected | missing`. + - `_mapFromDCDocumentVerificationStatus`: Preserves the full granularity of the backend status for the UI/presentation layer. + + #### Feature Repository — `DocumentsRepositoryImpl` + - Fixed to ensure all cross-cutting services (`FileUploadService`, `VerificationService`) are properly orchestrated. + - **Verification Integration**: When a document is uploaded, a verification job is triggered, and its `verificationId` is saved to Data Connect immediately. This allows the UI to show a "Processing" state while the background job runs. --- -## 5. DocumentStatus Mapping +## 5. DocumentStatus Mapping Reference -The backend uses a richer enum than the domain layer. The mapping in `_mapDocumentStatus` should be finalized as follows: +The backend uses a richer enum than the domain layer. The mapping is standardized as follows: | Backend `DocumentStatus` | Domain `DocumentStatus` | Notes | |--------------------------|-------------------------|-------| | `VERIFIED` | `verified` | Fully approved | +| `AUTO_PASS` | `verified` | AI approved | +| `APPROVED` | `verified` | Manually approved | | `UPLOADED` | `pending` | File received, not yet processed | | `PENDING` | `pending` | Queued for verification | | `PROCESSING` | `pending` | AI analysis in progress | -| `AUTO_PASS` | `verified` | AI approved | -| `APPROVED` | `verified` | Manually approved | | `NEEDS_REVIEW` | `pending` | AI unsure, human review needed | +| `EXPIRING` | `pending` | Approaching expiry (treated as pending for renewal) | +| `MISSING` | `missing` | Document not yet uploaded | | `AUTO_FAIL` | `rejected` | AI rejected | -| `REJECTED` | `rejected` | Manually rejected; check `rejectionReason` | +| `REJECTED` | `rejected` | Manually rejected | | `ERROR` | `rejected` | System error during verification | -| `EXPIRING` | `verified` | Valid but approaching expiry | --- @@ -172,3 +148,13 @@ DocumentUploadStatus **Cubit guards:** - Upload is blocked unless `state.isAttested == true` - Button is only enabled when both a file is selected AND attestation is checked + +--- + +## 7. Future Considerations: Certificates + +The implementation of the **Certificates** module should follow this exact pattern with a few key differences: +1. **Repository**: Use `StaffConnectorRepository.getStaffCertificates()` and `upsertStaffCertificate()`. +2. **Metadata**: Certificates often require an `expiryDate` and `issueingBody` which should be captured during the upload step if not already present in the schema. +3. **Verification**: If using the same `VerificationService`, ensure the `category` is set to `CERTIFICATE` instead of `DOCUMENT` to trigger appropriate verification logic. +4. **UI**: Mirror the `DocumentUploadPage` design but update the instructions and translation keys to reference certificates. diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/data/repositories_impl/documents_repository_impl.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/data/repositories_impl/documents_repository_impl.dart index 385d59f6..c32e3e88 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/data/repositories_impl/documents_repository_impl.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/data/repositories_impl/documents_repository_impl.dart @@ -7,40 +7,23 @@ import '../../domain/repositories/documents_repository.dart'; /// Implementation of [DocumentsRepository] using Data Connect. class DocumentsRepositoryImpl implements DocumentsRepository { - DocumentsRepositoryImpl() : _service = DataConnectService.instance; + DocumentsRepositoryImpl({ + required FileUploadService uploadService, + required SignedUrlService signedUrlService, + required VerificationService verificationService, + }) : _service = DataConnectService.instance, + _uploadService = uploadService, + _signedUrlService = signedUrlService, + _verificationService = verificationService; + final DataConnectService _service; + final FileUploadService _uploadService; + final SignedUrlService _signedUrlService; + final VerificationService _verificationService; @override Future> getDocuments() async { - return _service.run(() async { - final String staffId = await _service.getStaffId(); - - /// MOCK IMPLEMENTATION - /// To be replaced with real data connect query when available - return [ - domain.StaffDocument( - id: 'doc1', - staffId: staffId, - documentId: 'd1', - name: 'Work Permit', - description: 'Valid work permit document', - status: domain.DocumentStatus.verified, - documentUrl: 'https://example.com/documents/work_permit.pdf', - expiryDate: DateTime.now().add(const Duration(days: 365)), - ), - domain.StaffDocument( - id: 'doc2', - staffId: staffId, - documentId: 'd2', - name: 'Health and Safety Training', - description: - 'Certificate of completion for health and safety training', - status: domain.DocumentStatus.pending, - documentUrl: 'https://example.com/documents/health_safety.pdf', - expiryDate: DateTime.now().add(const Duration(days: 180)), - ), - ]; - }); + return _service.getStaffRepository().getStaffDocuments(); } @override @@ -48,70 +31,63 @@ class DocumentsRepositoryImpl implements DocumentsRepository { String documentId, String filePath, ) async { - // Mock upload delay - await Future.delayed(const Duration(seconds: 2)); + return _service.run(() async { + // 1. Upload the file to cloud storage + final FileUploadResponse uploadRes = await _uploadService.uploadFile( + filePath: filePath, + fileName: 'staff_document_${DateTime.now().millisecondsSinceEpoch}.pdf', + visibility: domain.FileVisibility.private, + ); - final String staffId = await _service.getStaffId(); - return domain.StaffDocument( - id: 'mock_uploaded_${DateTime.now().millisecondsSinceEpoch}', - staffId: staffId, - documentId: documentId, - name: 'Uploaded Document', - status: domain.DocumentStatus.pending, - documentUrl: 'https://example.com/mock.pdf', - ); - } + // 2. Generate a signed URL for verification service to access the file + final SignedUrlResponse signedUrlRes = await _signedUrlService + .createSignedUrl(fileUri: uploadRes.fileUri); - domain.StaffDocument _mapToDomain( - ListStaffDocumentsByStaffIdStaffDocuments doc, - ) { - return domain.StaffDocument( - id: doc.id, - staffId: doc.staffId, - documentId: doc.documentId, - name: doc.document.name, - description: null, // Description not available in data source - status: _mapStatus(doc.status), - documentUrl: doc.documentUrl, - expiryDate: doc.expiryDate == null - ? null - : DateTimeUtils.toDeviceTime(doc.expiryDate!.toDateTime()), - ); + // 3. Initiate verification + final String staffId = await _service.getStaffId(); + final VerificationResponse verificationRes = await _verificationService + .createVerification( + fileUri: uploadRes.fileUri, + type: documentId, // Assuming documentId aligns with type + subjectType: 'STAFF', + subjectId: staffId, + ); + + // 4. Update/Create StaffDocument in Data Connect + await _service.getStaffRepository().upsertStaffDocument( + documentId: documentId, + documentUrl: uploadRes.fileUri, + status: domain.DocumentStatus.pending, + verificationId: verificationRes.verificationId, + ); + + // 5. Return the updated document state + final List documents = await getDocuments(); + return documents.firstWhere( + (domain.StaffDocument d) => d.documentId == documentId, + ); + }); } domain.DocumentStatus _mapStatus(EnumValue status) { if (status is Known) { switch (status.value) { case DocumentStatus.VERIFIED: + case DocumentStatus.AUTO_PASS: + case DocumentStatus.APPROVED: return domain.DocumentStatus.verified; case DocumentStatus.PENDING: + case DocumentStatus.UPLOADED: + case DocumentStatus.PROCESSING: + case DocumentStatus.NEEDS_REVIEW: + case DocumentStatus.EXPIRING: return domain.DocumentStatus.pending; case DocumentStatus.MISSING: return domain.DocumentStatus.missing; - case DocumentStatus.UPLOADED: - case DocumentStatus.EXPIRING: - return domain.DocumentStatus.pending; - case DocumentStatus.PROCESSING: - // TODO: Handle this case. - throw UnimplementedError(); - case DocumentStatus.AUTO_PASS: - // TODO: Handle this case. - throw UnimplementedError(); case DocumentStatus.AUTO_FAIL: - // TODO: Handle this case. - throw UnimplementedError(); - case DocumentStatus.NEEDS_REVIEW: - // TODO: Handle this case. - throw UnimplementedError(); - case DocumentStatus.APPROVED: - // TODO: Handle this case. - throw UnimplementedError(); case DocumentStatus.REJECTED: - // TODO: Handle this case. - throw UnimplementedError(); case DocumentStatus.ERROR: - // TODO: Handle this case. - throw UnimplementedError(); + return domain.DocumentStatus.rejected; } } // Default to pending for Unknown or unhandled cases diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/staff_documents_module.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/staff_documents_module.dart index 900d4750..58dda25d 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/staff_documents_module.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/staff_documents_module.dart @@ -13,7 +13,13 @@ import 'presentation/pages/document_upload_page.dart'; class StaffDocumentsModule extends Module { @override void binds(Injector i) { - i.addLazySingleton(DocumentsRepositoryImpl.new); + i.addLazySingleton( + () => DocumentsRepositoryImpl( + uploadService: i.get(), + signedUrlService: i.get(), + verificationService: i.get(), + ), + ); i.addLazySingleton(GetDocumentsUseCase.new); i.addLazySingleton(UploadDocumentUseCase.new); diff --git a/backend/dataconnect/connector/staffDocument/mutations.gql b/backend/dataconnect/connector/staffDocument/mutations.gql index ebec5fba..eba48ff3 100644 --- a/backend/dataconnect/connector/staffDocument/mutations.gql +++ b/backend/dataconnect/connector/staffDocument/mutations.gql @@ -58,6 +58,7 @@ mutation upsertStaffDocument( $status: DocumentStatus! $documentUrl: String $expiryDate: Timestamp + $verificationId: String ) @auth(level: USER) { staffDocument_upsert( data: { @@ -67,6 +68,7 @@ mutation upsertStaffDocument( status: $status documentUrl: $documentUrl expiryDate: $expiryDate + verificationId: $verificationId } ) } diff --git a/backend/dataconnect/connector/staffDocument/queries.gql b/backend/dataconnect/connector/staffDocument/queries.gql index 77e6cc6f..e0660353 100644 --- a/backend/dataconnect/connector/staffDocument/queries.gql +++ b/backend/dataconnect/connector/staffDocument/queries.gql @@ -11,6 +11,7 @@ query getStaffDocumentByKey( status documentUrl expiryDate + verificationId createdAt updatedAt document { @@ -39,6 +40,7 @@ query listStaffDocumentsByStaffId( status documentUrl expiryDate + verificationId createdAt updatedAt document {