# Certificate Upload & Verification Workflow

This document outlines the standardized workflow for handling certificate uploads, metadata capture, and persistence within the Krow mobile application. The certificates module follows the same layered architecture as the `documents` module (see `documents/IMPLEMENTATION_WORKFLOW.md`), with key differences to accommodate certificate-specific metadata (expiry date, issuer, certificate number, type).

## 1. Overview

The workflow follows a 4-step lifecycle:

1. **Form Entry**: The user fills in certificate metadata (name, type, issuer, certificate number, expiry date).
2. **File Selection**: Picking a PDF, JPG, or PNG file locally via `FilePickerService`.
3. **Attestation**: Requiring the user to confirm the certificate is genuine before submission.
4. **Upload & Persistence**: Pushing the file to storage, initiating background verification, and saving the record to the database via Data Connect.

---

## 2. Technical Stack

| Service | Responsibility |
|---------|----------------|
| `FilePickerService` | PDF/image file selection from device |
| `FileUploadService` | Uploads raw files to secure cloud storage |
| `SignedUrlService` | Generates secure internal links for verification access |
| `VerificationService` | Orchestrates AI or manual verification (category: `CERTIFICATE`) |
| `DataConnect` (Firebase) | Persists structured data and verification metadata via `upsertStaffCertificate` |

---

## 3. Key Difference vs. Documents Module

| Aspect | Documents | Certificates |
|--------|-----------|--------------|
| Metadata captured | None (just a file) | `name`, `type` (`ComplianceType`), `expiryDate`, `issuer`, `certificateNumber` |
| Accepted file types | `pdf` only | `pdf`, `jpg`, `png` |
| Verification category | `DOCUMENT` | `CERTIFICATE` |
| Domain entity | `StaffDocument` | `StaffCertificate` |
| Status enum | `DocumentStatus` | `StaffCertificateStatus` |
| Validation status | `DocumentVerificationStatus` | `StaffCertificateValidationStatus` |
| Repository | `DocumentsRepository` | `CertificatesRepository` |
| Data Connect method | `upsertStaffDocument` | `upsertStaffCertificate` |
| Navigator helper | `StaffNavigator.toDocumentUpload` | `StaffNavigator.toCertificateUpload` |

---

## 4. Implementation Status

### ✅ Completed — Presentation Layer

#### Routing
- `StaffPaths.certificateUpload` constant used in `StaffPaths.childRoute` within `StaffCertificatesModule`.
- `StaffNavigator.toCertificateUpload({StaffCertificate? certificate})` type-safe navigation helper wires the route argument.

#### Domain Layer
- `CertificatesRepository` interface defined with three methods:
  - `getCertificates()` — fetch the current staff member's certificates.
  - `uploadCertificate(...)` — upload file + trigger verification + persist record.
  - `upsertCertificate(...)` — metadata-only update (no file re-upload).
  - `deleteCertificate(...)` — remove a certificate by `ComplianceType`.
- `UploadCertificateUseCase` wraps `uploadCertificate`; takes `UploadCertificateParams`:
  - `certificationType` (`ComplianceType`) — required
  - `name` (`String`) — required
  - `filePath` (`String`) — required
  - `expiryDate` (`DateTime?`) — optional
  - `issuer` (`String?`) — optional
  - `certificateNumber` (`String?`) — optional
- `GetCertificatesUseCase` wraps `getCertificates()`.
- `UpsertCertificateUseCase` wraps `upsertCertificate()` for metadata-only saves.
- `DeleteCertificateUseCase` wraps `deleteCertificate()`.

#### State Management
- `CertificateUploadStatus` enum: `initial | uploading | success | failure`
- `CertificateUploadState` (Equatable): tracks `status`, `isAttested`, `updatedCertificate`, `errorMessage`
- `CertificateUploadCubit`:
  - Guards upload behind `state.isAttested == true`.
  - On success: emits `success` with the returned `StaffCertificate`.
  - On failure: emits `failure` with the error message key via `BlocErrorHandler`.

#### UI — `CertificateUploadPage`

Accepts an optional `StaffCertificate? certificate` as a route argument. When provided, the form is pre-populated for editing; when `null`, the page is in "new certificate" mode.

**Form fields:**
- **Certificate Name** (`TextEditingController _nameController`) — required.
- **Certificate Type** (`ComplianceType? _selectedType`) — `DropdownButton`, defaults to `ComplianceType.other`.
- **Issuer** (`TextEditingController _issuerController`) — optional.
- **Certificate Number** (`TextEditingController _numberController`) — optional.
- **Expiry Date** (`DateTime? _selectedExpiryDate`) — date picker; defaults to 1 year from today.

**File selection:**
- `FilePickerService.pickFile(allowedExtensions: ['pdf', 'jpg', 'png'])`.
- Selected file path stored in `String? _selectedFilePath`.

**Attestation & submission:**
- Attestation checkbox must be checked before submitting (mirrors Documents pattern).
- Submit button enabled only when: a file is selected AND attestation is checked.
- Loading state: `CircularProgressIndicator` replaces the submit button while `CertificateUploadStatus.uploading`.
- On `success`: shows `UiSnackbar` (success type) and calls `Modular.to.pop()`.
- On `failure`: shows `UiSnackbar` (error type); stays on page for retry.

**App bar:**
- `UiAppBar` with `certificate?.name ?? t.staff_certificates.upload_modal.title` as title.

#### UI Guidelines (Consistent with Documents)
Follow the same patterns defined in `documents/IMPLEMENTATION_WORKFLOW.md §3`:
1. **Header & Instructions**: `UiAppBar` title = certificate name (or modal title). Body instructions use `UiTypography.body1m.textPrimary`.
2. **File Selection Card**:
   - Empty: neutral/primary bordered card inviting file pick.
   - Selected: `UiColors.bgPopup` card with `UiConstants.radiusLg` rounding, `UiColors.primary` border, truncated file name, and explicit "Replace" action.
3. **Bottom Footer / Attestation**: Fixed to `bottomNavigationBar` inside `SafeArea` + `Padding`; submit state tightly coupled to both file presence and attestation.

#### Module Wiring — `StaffCertificatesModule`

Binds (all lazy singletons unless noted):
| Binding | Scope |
|---------|-------|
| `CertificatesRepository` → `CertificatesRepositoryImpl` | Lazy singleton |
| `GetCertificatesUseCase` | Lazy singleton |
| `DeleteCertificateUseCase` | Lazy singleton |
| `UpsertCertificateUseCase` | Lazy singleton |
| `UploadCertificateUseCase` | Lazy singleton |
| `CertificatesCubit` | Lazy singleton |
| `CertificateUploadCubit` | **Per-use** (non-singleton via `i.add<>`) |

Routes:
- `StaffPaths.childRoute(StaffPaths.certificates, StaffPaths.certificates)` → `CertificatesPage`
- `StaffPaths.childRoute(StaffPaths.certificates, StaffPaths.certificateUpload)` → `CertificateUploadPage(certificate: r.args.data is StaffCertificate ? … : null)`

#### `CertificatesPage` Integration
- `CertificateCard.onTap` navigates to `CertificateUploadPage` with the `StaffCertificate` as route data.
- After returning from the upload page, `loadCertificates()` is awaited to refresh the list (fixes `unawaited_futures` lint).

#### Localization
- Keys live under `staff_certificates.*` in `en.i18n.json` and `es.i18n.json`.
- Relevant keys: `upload_modal.title`, `upload_modal.success_snackbar`, `upload_modal.name_label`, `upload_modal.replace`, `error_loading`.
- Codegen: `dart run slang` generates `TranslationsStaffCertificatesEn` and its Spanish counterpart.

---

### ✅ Completed — Data Layer

#### Data Connect Integration
- `StaffConnectorRepository` interface includes `getStaffCertificates()` and `upsertStaffCertificate()`.
- `deleteStaffCertificate(certificationType:)` implemented for certificate removal.
- SDK regenerated via: `make dataconnect-generate-sdk ENV=dev`.

#### Repository Implementation — `CertificatesRepositoryImpl`

Constructor injects `FileUploadService`, `SignedUrlService`, `VerificationService`; uses `DataConnectService.instance` for Data Connect calls.

**`uploadCertificate()` — 5-step orchestration:**

```
1. FileUploadService.uploadFile(...)
     → fileName: 'staff_cert_<type>_<timestamp>.pdf'
     → visibility: FileVisibility.private
   Returns FileUploadResponse { fileUri }

2. SignedUrlService.createSignedUrl(fileUri: uploadRes.fileUri)
     → Generates an internal signed link for verification access

3. VerificationService.createVerification(...)
     → fileUri, type: certificationType.value
     → category: 'CERTIFICATE'   ← distinguishes from DOCUMENT
     → subjectType: 'STAFF'
     → subjectId: await _service.getStaffId()

4. StaffConnectorRepository.upsertStaffCertificate(...)
     → certificationType, name, status: pending
     → fileUrl: uploadRes.fileUri, expiry, issuer, certificateNumber
     → validationStatus: pendingExpertReview

5. getCertificates() → find & return the matching StaffCertificate
```

**`upsertCertificate()` — metadata-only update:**
- Directly calls `upsertStaffCertificate(...)` without any file upload or verification step.
- Used for editing certificate details after initial upload.

**`deleteCertificate()` — record removal:**
- Calls `deleteStaffCertificate(certificationType:)`.

---

## 5. State Management Reference

```
CertificateUploadStatus
  ├── initial    — page just opened / form being filled
  ├── uploading  — upload + verification in progress
  ├── success    — certificate saved; navigate back (pop)
  └── failure    — error; stay on page; show snackbar
```

**Cubit guards:**
- Upload is blocked unless `state.isAttested == true`.
- Submit button enabled only when both a file is selected AND attestation is checked.
- Uses `BlocErrorHandler` mixin for consistent error emission.

---

## 6. StaffCertificateStatus Mapping Reference

The backend uses a richer enum than the domain layer. Standard mapping:

| Backend / Validation Status | Domain `StaffCertificateStatus` | Notes |
|-----------------------------|----------------------------------|-------|
| `VERIFIED` / `AUTO_PASS` / `APPROVED` | `verified` | Fully or AI-approved |
| `UPLOADED` / `PENDING` / `PROCESSING` / `NEEDS_REVIEW` / `EXPIRING` | `pending` | Upload received; processing or awaiting renewal |
| `AUTO_FAIL` / `REJECTED` / `ERROR` | `rejected` | AI or manual rejection / system error |
| `MISSING` | `missing` | Not yet uploaded |

`StaffCertificateValidationStatus` preserves the full backend granularity for detailed UI feedback (e.g., showing "Pending Expert Review" vs. "Auto-Failed").

---

## 7. Future Considerations

1. **Expiry Notifications**: Certificates approaching expiry (domain status `pending` / backend `EXPIRING`) should surface a nudge in `CertificatesPage` or via push notification.
2. **Re-verification on Edit**: When a certificate's file is replaced via `uploadCertificate`, a new verification job is triggered. Decide whether editing only metadata (via `upsertCertificate`) should also trigger re-verification.
3. **Multiple Files per Certificate**: The current schema supports a single `fileUrl`. If multi-page certificates become a requirement, the Data Connect schema and `CertificatesRepository` interface will need extending.
4. **Shared Compliance UI Components**: The file selection card and attestation footer patterns are duplicated between `DocumentUploadPage` and `CertificateUploadPage`. Consider extracting them into a shared `compliance_upload_widgets` package to reduce duplication.
