Skip to main content

Domain 08: Quality Assessment

Domain Owner: Backend (ASP.NET Core) / Mobile (Flutter offline-first) Last Updated: 2026-03-10 Workflows: WF-QA-01 through WF-QA-04

Domain Introduction

The Quality Assessment domain governs how agricultural produce is inspected, graded, and accepted (or rejected) at collection points. It is the bridge between field-level crop batch creation and warehouse stock recording: a crop batch enters the system via StartAssessmentAsync, is evaluated against a dynamic question checklist, and -- upon completion -- triggers a StockIn movement that feeds into the Stock Management domain (see WF-STOCK-01).

Key architectural principles:

  • Dynamic checklists per crop category: Assessment questions are configured per CropCategory (Cereals, Tubers, Fruits/Vegetables, Coffee/Cacao, Legumes) with category-specific QC fields. Questions support four types: Numeric, Boolean, SelectOptions, and Date.
  • Offline-first with draft auto-save: The mobile client stores assessment drafts locally with a 2-second debounce. If the device is offline when the agent completes the form, the assessment is queued in a local pending table and synced when connectivity returns.
  • LOCAL_ batch ID pattern: Batches created offline carry a LOCAL_ prefixed ID. The sync strategy resolves these to server-generated IDs before submitting answers.
  • StockIn on acceptance: When AcceptedQuantity > 0, CompleteAssessmentAsync calls IStockMovementRecorder.RecordMovementAsync to create a StockIn movement at the collection point -- directly linking quality assessments to inventory.
  • Delta sync for questions: The mobile client tracks a modifiedSince timestamp and fetches only updated questions, minimising bandwidth for field agents.
  • Photo pipeline: Photos are uploaded to DigitalOcean Spaces via /api/upload/image, then attached to the assessment via POST /api/quality-assessments/{id}/photos.

Domain Summary Diagram

graph LR
subgraph "Admin Panel"
QCONFIG((Configure Questions))
EXPORT[Export to Excel]
end

subgraph "Mobile Client"
SELECT_FARMER[Select Farmer]
START[Start Assessment]
FORM[Assessment Form + Photos]
DRAFT[[Local Draft Storage]]
PENDING[[Pending Assessment Queue]]
SYNC[Assessment Sync Strategy]
Q_SYNC[Question Delta Sync]
end

subgraph "Backend API"
QA_CTRL[QualityAssessmentsController]
QQ_CTRL[QualityAssessmentQuestionsController]
QA_SVC[QualityAssessmentService]
STOCK_REC[IStockMovementRecorder]
end

subgraph "Storage"
DB[[AlmafricaDbContext]]
SPACES[[DigitalOcean Spaces]]
end

QCONFIG -->|CRUD| QQ_CTRL
QQ_CTRL --> DB
EXPORT -->|GET /export| QQ_CTRL

SELECT_FARMER --> START
START -->|POST /start| QA_CTRL
START --> FORM
FORM -->|auto-save| DRAFT
FORM -->|complete| QA_CTRL
FORM -.->|offline| PENDING

PENDING --> SYNC
SYNC -->|POST /complete| QA_CTRL
SYNC -->|upload photos| SPACES
SYNC -->|attach URLs| QA_CTRL

Q_SYNC -->|GET /questions| QA_CTRL

QA_CTRL --> QA_SVC
QA_SVC -->|StockIn movement| STOCK_REC
QA_SVC --> DB
STOCK_REC --> DB

Workflows


WF-QA-01: Assessment Question Configuration

Trigger: Admin creates or updates assessment questions via the admin panel Frequency: On-demand (configuration changes by admin users) Offline Support: No (admin panel is web-only)

Workflow Diagram

graph TD
A((Admin opens question manager)) --> B{Create or Update?}

B -->|Create| C[POST /api/admin/quality-assessment-questions]
B -->|Update| D[PUT /api/admin/quality-assessment-questions/:id]
B -->|Delete| E[DELETE /api/admin/quality-assessment-questions/:id]

C --> F[QualityAssessmentQuestionService.CreateQuestionAsync]
D --> G[QualityAssessmentQuestionService.UpdateQuestionAsync]
E --> H[QualityAssessmentQuestionService.DeleteQuestionAsync]

F --> I{Validate constraints}
G --> I
I -->|SelectOptions type - no options| J[Return 400: Options required]
I -->|Numeric - MinValue >= MaxValue| K[Return 400: Min must be less than Max]
I -->|Missing en/fr translations| L[Return 400: en and fr translations required]
I -->|Valid| M[Save to QualityAssessmentQuestions table]

H --> N[Soft-delete: set IsDeleted = true]
N --> O[[AlmafricaDbContext.SaveChangesAsync]]

M --> P{Has Options?}
P -->|Yes| Q[Save QualityAssessmentQuestionOptions with translations]
P -->|No| R[Save question translations only]
Q --> O
R --> O

O --> S[Return QuestionDto to admin]
S --> T((Mobile clients pick up changes via delta sync - WF-QA-03))

Node Descriptions

#n8n Node TypeComponentFunctionError Handling
1Manual TriggerAdmin Panel UIAdmin initiates question CRUD actionValidation errors shown inline
2HTTP RequestQualityAssessmentQuestionsControllerRoutes CRUD to service; requires AdminPolicyReturns 403 if user lacks admin role
3FunctionQualityAssessmentQuestionService.CreateQuestionAsync()Validates constraints, creates question + options + translationsReturns Result.Failure with descriptive message
4FunctionQualityAssessmentQuestionService.UpdateQuestionAsync()Replaces options (delete old, insert new), updates translations404 if question not found or soft-deleted
5FunctionQualityAssessmentQuestionService.DeleteQuestionAsync()Soft-delete via ISoftDelete.IsDeleted = true404 if question not found
6FunctionValidateQuestionConstraints()Ensures SelectOptions has options, Numeric has valid min/max rangeCollects all errors into a list, returns joined message
7DatabaseAlmafricaDbContext.SaveChangesAsync()Persists question, options, and translation recordsDbUpdateException on constraint violation

Data Transformations

  • Input (Create): CreateQuestionRequest { CropCategoryId, QuestionText, QuestionType, IsRequired, DisplayOrder, MinValue?, MaxValue?, UnitId?, Options[], Translations[] }
  • Processing: Validate constraints (type-specific rules), create QualityAssessmentQuestion entity with child QualityAssessmentQuestionOption entities and QualityAssessmentQuestionTranslation / QualityAssessmentQuestionOptionTranslation records
  • Output: QualityAssessmentQuestionDto with ID, all fields, nested options and translations
  • Export: GET /api/admin/quality-assessment-questions/export produces an Excel file via IExcelExportService

Error Handling

  • Missing translations: Both en and fr translations are required; validation rejects requests missing either language
  • Invalid type/option combo: SelectOptions type must have at least one option; Numeric type must have MinValue < MaxValue when both are specified
  • Soft-delete protection: Deleted questions (ISoftDelete) are excluded from all query results; hard deletes are never performed
  • Concurrent modification: EF Core optimistic concurrency via UpdatedAt timestamps

Cross-References

  • Consumed by: WF-QA-02 (questions displayed in the assessment form)
  • Synced to mobile via: WF-QA-03 (delta sync picks up new/updated questions)

WF-QA-02: Field Assessment Execution

Trigger: Field agent selects a farmer and initiates a quality assessment Frequency: On-demand (per crop batch delivery at a collection point) Offline Support: Full -- batch creation, form answers, draft save, and completion all work offline

Workflow Diagram

graph TD
A((Agent taps 'New Assessment')) --> B{Permission check}
B -->|canCreateAssessments = false| C[Show unauthorized message]
B -->|canCreateAssessments = true| D[SelectFarmerForAssessmentScreen]

D --> E[Search / filter farmers via FarmerLocalService]
E --> F[Agent selects a farmer]
F --> G[StartAssessmentScreen]

G --> H[Select crop category + crop]
H --> I[Enter quantity + unit]
I --> J[Select collection point]
J --> K{Production cycle match?}
K -->|Yes - link existing| L[Set productionCycleId]
K -->|No match or skip| M[Continue without cycle]

L --> N{Device online?}
M --> N
N -->|Yes| O[POST /api/quality-assessments/start]
N -->|No| P[Create LOCAL_ prefixed batch ID]
O --> Q[Backend: StartAssessmentAsync]
Q --> R[Validate farmer approved + crop + collection point + unit]
R --> S[Generate batch number via GenerateBatchNumberAsync]
S --> T[Create CropBatch - status: PendingAssessment]
T --> U[Create empty QualityAssessment record]
U --> V[Return BatchCreatedResponse with batchId + batchNumber]

P --> W[Store batch locally with LOCAL_ ID]
V --> X[Navigate to AssessmentFormScreen]
W --> X

X --> Y[Load questions for crop category]
Y --> Z[AssessmentFormProvider manages form state]

Z --> AA[Agent answers questions]
AA --> AB{Answer type?}
AB -->|Numeric| AC[setNumericAnswer - validate min/max]
AB -->|Boolean| AD[setBooleanAnswer]
AB -->|SelectOptions| AE[setSelectOptionAnswer]
AB -->|Date| AF[setDateAnswer]

AC --> AG[_scheduleDraftSave - 2s debounce]
AD --> AG
AE --> AG
AF --> AG
AG --> AH[[Save draft to local SQLite]]

AA --> AI[Capture photos via camera]
AI --> AJ[Store photo XFiles in provider]

AA --> AK[Enter accepted + rejected quantities]
AK --> AL{isQuantityValid?}
AL -->|accepted + rejected != initial| AM[Show validation error]
AL -->|Valid| AN[Agent taps Complete]

AN --> AO{Device online?}
AO -->|Yes| AP[POST /api/quality-assessments/complete]
AP --> AQ[Backend: CompleteAssessmentAsync]
AQ --> AR[Validate batch not already completed]
AR --> AS[Validate quantities: accepted + rejected = initial within 0.01 tolerance]
AS --> AT[Determine OverallResult: Accepted / PartiallyAccepted / FullyRejected]
AT --> AU[Save QualityAssessmentAnswers]
AU --> AV[batch.MarkAsAssessed]
AV --> AW{AcceptedQuantity > 0?}
AW -->|Yes| AX[StockMovementRecorder.RecordMovementAsync - StockIn]
AW -->|No| AY[Skip StockIn movement]
AX --> AZ[[SaveChangesAsync]]
AY --> AZ
AZ --> BA[Return QualityAssessmentDto]
BA --> BB[Clear local draft]

AO -->|No| BC[Save to pending_assessments table]
BC --> BD[Mark for sync - WF-QA-03]
BD --> BB
BB --> BE((Assessment complete - navigate back))

Node Descriptions

#n8n Node TypeComponentFunctionError Handling
1Manual TriggerAgent Home UIAgent taps "New Assessment" buttonN/A
2FunctionRoleManager.canCreateAssessments()Check CreateAssessments permission (storage-first, fallback to feature matrix)Shows unauthorized message if false
3ScreenSelectFarmerForAssessmentScreenFarmer search with location/cooperative/recent filters via FarmerLocalServiceEmpty state if no farmers match
4ScreenStartAssessmentScreenCollect crop category, crop, quantity, unit, collection point, notesValidates required fields before proceeding
5HTTP RequestAssessmentRepositoryImpl.startAssessment()POST /api/quality-assessments/startFalls back to local batch creation on network error
6FunctionQualityAssessmentService.StartAssessmentAsync()Validate farmer (approved), crop, collection point, unit; create CropBatch + empty QualityAssessmentReturns Result.NotFound or Result.Failure
7FunctionGenerateBatchNumberAsync()Generate unique batch numberConcurrency-safe via DB query
8ScreenAssessmentFormScreenDynamic question rendering, photo capture, quantity acceptanceShows question-level validation errors
9FunctionAssessmentFormProviderState manager: answers map, draft auto-save (2s debounce), quantity validationCatches and surfaces all error types
10FunctionAssessmentFormProvider.completeAssessment()Build CompleteAssessmentRequest, call repository, clear draft on successParses API errors, quantity mismatch, network, auth errors
11FunctionQualityAssessmentService.CompleteAssessmentAsync()Validate quantities, determine result, save answers, trigger StockInResult.Failure for quantity mismatch, already-completed, missing checklist
12FunctionIStockMovementRecorder.RecordMovementAsync()Create StockIn movement at collection point for accepted quantityErrors propagate and roll back the transaction
13DatabaseAlmafricaDbContext.SaveChangesAsync()Persist assessment, answers, batch update, stock movement atomicallyTransaction rollback on failure

Data Transformations

  • Start Input: StartAssessmentRequest { FarmerId, CropId, CollectionPointId, InitialQuantity, QuantityUnitId, Notes? }
  • Start Output: BatchCreatedResponse { BatchId, BatchNumber, Message }
  • Complete Input: CompleteAssessmentRequest { CropBatchId, AcceptedQuantity, RejectedQuantity, RejectionReason?, Notes?, ExpiryDate?, CorrectiveActions?, Answers[] }
  • Each Answer: { QuestionId, QuestionType, NumericValue?, BooleanValue?, SelectedOptionId?, DateValue?, TextValue? }
  • Complete Output: QualityAssessmentDto with OverallResult enum: Accepted, PartiallyAccepted, FullyRejected

Error Handling

  • Farmer not approved: Backend returns 400 with message "Farmer is not approved for quality assessments"
  • Quantity mismatch: AcceptedQuantity + RejectedQuantity must equal InitialQuantity within 0.01 tolerance; returns descriptive error
  • Already completed: If AssessmentStatus is Completed or FullyRejected, returns "Assessment has already been completed"
  • No active checklist: If no ChecklistVersion exists for the crop category, returns 400
  • Offline completion: Saved to pending_assessments table with PendingAssessmentStatus.pending; picked up by WF-QA-03
  • Draft persistence failure: Logged but non-fatal; agent can still complete the form

Cross-References

  • Uses questions from: WF-QA-01 (question configuration)
  • Triggers stock recording: WF-STOCK-01 (StockIn movement via IStockMovementRecorder)
  • Offline sync via: WF-QA-03 (pending assessment sync)
  • Production cycle link: Assessment can link to a production cycle via productionCycleId on StartAssessmentScreen

WF-QA-03: Assessment Sync Flow

Trigger: Connectivity restored after offline assessment completion, or periodic sync Frequency: Event-driven (on connectivity change) and periodic (after main data sync) Offline Support: This workflow IS the offline recovery mechanism

Workflow Diagram

graph TD
A((Sync triggered)) --> B[AssessmentSyncIntegration.syncPendingAssessments]
B --> C[AssessmentSyncStrategy.syncPendingAssessments]
C --> D[Load pending + retryable-failed assessments from SQLite]
D --> E{Any pending?}
E -->|No| F[Return AssessmentSyncResult - success, 0 attempted]
E -->|Yes| G[Iterate assessments with progress stream]

G --> H[_syncSingleAssessment]
H --> I{Batch ID starts with LOCAL_?}
I -->|Yes| J[POST /api/quality-assessments/start to create server batch]
J --> K{Server batch created?}
K -->|Yes| L[Replace LOCAL_ ID with server batchId]
K -->|No| M[Mark assessment as failed + increment retryCount]
I -->|No| N[Use existing server batchId]

L --> O[Build CompleteAssessmentRequest from local data]
N --> O

O --> P{Has photos?}
P -->|Yes| Q[AssessmentPhotoService.uploadPhotos]
Q --> R[POST /api/upload/image per photo to DigitalOcean Spaces]
R --> S[Collect uploaded photo URLs]
S --> T[POST /api/quality-assessments/:id/photos to attach URLs]
P -->|No| U[Skip photo upload]

T --> V[POST /api/quality-assessments/complete]
U --> V
V --> W[Backend: CompleteAssessmentAsync]
W --> X{Success?}
X -->|Yes| Y[Delete from pending_assessments table]
Y --> Z[Emit SyncStage.completed progress]
X -->|No| AA{Retry count < 3?}
AA -->|Yes| AB[Increment retryCount, mark retryable]
AB --> AC[Emit SyncStage.retrying progress]
AA -->|No| AD[Mark as permanentlyFailed]
AD --> AE[Emit SyncStage.failed progress]

Z --> AF{More pending?}
AC --> AF
AE --> AF
AF -->|Yes| G
AF -->|No| AG[Return AssessmentSyncResult]
AG --> AH((Sync complete))

subgraph "Question Delta Sync"
QS((Main data sync completes)) --> QA[AssessmentQuestionsSyncHelper.syncAssessmentQuestions]
QA --> QB{SyncConfig.isAssessmentQuestionsFresh?}
QB -->|Yes - cache fresh| QC[Skip sync]
QB -->|No - stale| QD[GET /api/quality-assessments/questions?modifiedSince=timestamp]
QD --> QE{Response has questions?}
QE -->|Yes - delta| QF[Merge updated questions into local SQLite]
QE -->|No changes| QG[Update last sync timestamp only]
QF --> QH[Update SyncMetadataService + local sync time]
QG --> QH
QH --> QI((Questions up to date))
end

Node Descriptions

#n8n Node TypeComponentFunctionError Handling
1TriggerAssessmentSyncIntegration.syncPendingAssessments()Entry point; delegates to strategy, exposes progress streamCatches and logs top-level errors
2FunctionAssessmentSyncStrategy.syncPendingAssessments()Loads pending + retryable assessments, iterates with progressReturns AssessmentSyncResult with counts
3FunctionAssessmentSyncStrategy._syncSingleAssessment()Handles LOCAL_ ID resolution, builds request, uploads photos, calls completeCatches per-assessment errors, marks failed
4HTTP RequestAssessmentRemoteDataSource.startAssessment()POST /api/quality-assessments/start for LOCAL_ batch resolutionAssessmentApiException with isRetryable flag
5HTTP RequestAssessmentPhotoService.uploadPhotos()Sequential POST /api/upload/image per photo (multipart/form-data)Per-photo error logging; continues with remaining photos
6HTTP RequestAssessmentPhotoService.attachPhotosToAssessment()POST /api/quality-assessments/{id}/photos with URL listLogged; non-fatal (assessment still valid without photos)
7HTTP RequestAssessmentRemoteDataSource.completeAssessment()POST /api/quality-assessments/complete with full payloadAssessmentApiException checked for retryability
8DatabaseAssessmentLocalDataSourceDelete synced assessments, update retry counts, manage pending queueSQLite errors caught and logged
9FunctionAssessmentQuestionsSyncHelper.syncAssessmentQuestions()Bulk delta sync via modifiedSince parameterFalls back to full sync on error
10HTTP RequestAssessmentRemoteDataSource.getAllQuestions()GET /api/quality-assessments/questions?modifiedSince=...Network errors skip sync silently

Data Transformations

  • Pending assessment (local): SQLite row with batchId, farmerId, cropCategoryId, answers JSON, photo file paths, retryCount, status
  • LOCAL_ batch resolution: LOCAL_{uuid} batch ID replaced with server-generated Guid after startAssessment call
  • Photo upload pipeline: Local XFile path -> multipart upload to /api/upload/image -> returned URL string -> attached via /api/quality-assessments/{id}/photos
  • Question delta sync: modifiedSince ISO timestamp -> server returns only changed QualityAssessmentQuestionDto[] -> merged into local SQLite via saveQuestions()
  • Sync result: AssessmentSyncResult { success: bool, totalAttempted: int, successCount: int, failureCount: int }

Error Handling

  • LOCAL_ batch creation failure: Assessment marked as failed with incremented retryCount; retried on next sync cycle
  • Photo upload failure: Logged and continued; the assessment can still be completed without photos
  • Complete API failure (retryable): retryCount < 3 -> mark for retry; retryCount >= 3 -> mark as permanentlyFailed
  • Idempotency: OfflineCommandMetadata headers ensure duplicate submissions are safely handled by the backend
  • Question sync failure: Caught silently; mobile continues using cached questions until next successful sync
  • Token expiry during sync: AssessmentPhotoService Dio interceptor handles 401 with automatic token refresh

Cross-References

  • Syncs assessments created in: WF-QA-02 (field assessment execution)
  • Triggers on backend: WF-STOCK-01 (StockIn movement recorded during CompleteAssessmentAsync)
  • Called after: Main data sync cycle (syncAfterMainSync() extension method)
  • Question sync uses: SyncConfig.isAssessmentQuestionsFresh() and SyncMetadataService for cache management

WF-QA-04: Warehouse Pending Assessments

Trigger: Warehouse operator opens the pending assessments view for a collection point Frequency: On-demand (warehouse staff reviewing pending and completed assessments) Offline Support: Partial -- cached assessments viewable offline; requires connectivity for fresh data

Workflow Diagram

graph TD
A((Warehouse operator opens Pending Assessments)) --> B[WarehousePendingAssessmentsScreen]
B --> C[Receive collectionPointId + collectionPointName via args]
C --> D[Load assessments for collection point]

D --> E{Device online?}
E -->|Yes| F[GET /api/quality-assessments/batches/pending?collectionPointId=X]
E -->|No| G[Load from local completed_assessments cache]

F --> H[Backend: GetPendingBatchesAsync]
H --> I[Query CropBatches WHERE CollectionPointId = X AND AssessmentStatus = PendingAssessment]
I --> J[Apply search filter + pagination]
J --> K[Return PagedResult of CropBatchDto]

G --> L[Display cached assessment list]
K --> L

L --> M[Show tabs: Completed / Pending]
M --> N{Operator selects an assessment?}
N -->|Pending batch| O[Navigate to StartAssessmentScreen - WF-QA-02]
N -->|Completed assessment| P[View assessment details]

P --> Q[Display: result, quantities, answers, photos]
Q --> R{Has edit permission?}
R -->|canEditAssessments = true| S[Show Edit button]
R -->|No| T[Read-only view]

S --> U[Navigate to edit assessment flow]
U --> V[PUT /api/quality-assessments/:assessmentId]
V --> W[Backend: EditAssessmentAsync]
W --> X[Update answers, quantities, result]
X --> Y[[SaveChangesAsync]]
Y --> Z[Return updated QualityAssessmentDto]
Z --> AA((Refresh assessment list))

subgraph "Assessed Batches View"
AB((Operator views assessed batches)) --> AC[GET /api/quality-assessments/batches/assessed]
AC --> AD[Backend: GetAssessedBatchesAsync]
AD --> AE[Query with filters: farmerId, cropId, startDate, endDate, result, search]
AE --> AF[Return PagedResult of CropBatchDto with assessment details]
end

Node Descriptions

#n8n Node TypeComponentFunctionError Handling
1Manual TriggerWarehouse Home UIOperator navigates to pending assessments for a collection pointN/A
2ScreenWarehousePendingAssessmentsScreenDisplays pending and completed assessments with tabs; receives WarehousePendingAssessmentsArgsShows empty state or error messages
3HTTP RequestAssessmentRepositoryImpl.getPendingBatchesByCollectionPoint()GET /api/quality-assessments/batches/pending?collectionPointId=XFalls back to local cache on network error
4FunctionQualityAssessmentService.GetPendingBatchesAsync()Query CropBatches filtered by collection point, status = PendingAssessment, with search and pagingReturns empty paged result on no matches
5FunctionQualityAssessmentService.GetAssessedBatchesAsync()Query assessed batches with multi-field filters (farmer, crop, date range, result, search)Returns empty paged result on no matches
6HTTP RequestAssessmentRemoteDataSource.editAssessment()PUT /api/quality-assessments/{assessmentId}AssessmentApiException propagated to UI
7FunctionQualityAssessmentService.EditAssessmentAsync()Update existing assessment: answers, quantities, result, notes404 if assessment not found; 400 for validation errors
8FunctionRoleManager.canEditAssessments()Check EditAssessments permissionHides edit button if false

Data Transformations

  • Input (Pending): collectionPointId (Guid) + optional search string + page/pageSize
  • Output (Pending): PagedResult<CropBatchDto> with batch details, farmer name, crop, quantities, assessment status
  • Input (Assessed): QualityAssessmentFilterDto { FarmerId?, CropId?, StartDate?, EndDate?, OverallResult?, SearchTerm?, CollectionPointId?, Page, PageSize }
  • Output (Assessed): PagedResult<CropBatchDto> with full assessment details including result, accepted/rejected quantities
  • Edit Input: EditAssessmentRequest with updated answers, quantities, notes
  • Edit Output: Updated QualityAssessmentDto

Error Handling

  • Network failure on load: Falls back to locally cached assessments from completed_assessments table; shows stale-data indicator
  • Edit validation failure: Backend returns 400 with specific error (quantity mismatch, already-completed, etc.); displayed in UI snackbar
  • Permission denied: Edit button hidden if canEditAssessments is false; backend returns 403 if permission header is forged
  • Empty collection point: Shows empty state message; no error thrown

Cross-References

  • Assessment execution: WF-QA-02 (navigates to StartAssessmentScreen for pending batches)
  • Stock impact: WF-STOCK-01 (completed assessments with AcceptedQuantity > 0 already triggered StockIn via WF-QA-02/WF-QA-03)
  • Warehouse context: This workflow is accessed from the warehouse dashboard (accessible to warehouseManager and warehouseOperator roles)
  • Edit flow re-triggers: EditAssessmentAsync can update quantities and answers but does NOT re-trigger stock movements (stock adjustment is a separate flow)

Source File Reference

Backend

FilePurpose
backend/Almafrica.Domain/Entities/QualityAssessment.csCore assessment entity (1:1 with CropBatch)
backend/Almafrica.Domain/Entities/QualityAssessmentAnswer.csIndividual answer per question
backend/Almafrica.Domain/Entities/Configurations/QualityAssessmentQuestion.csQuestion definition with type, constraints, translations
backend/Almafrica.Domain/Entities/Configurations/QualityAssessmentQuestionOption.csSelect-option choices with translations
backend/Almafrica.Domain/Entities/Configurations/QualityAssessmentQuestionTranslation.csQuestion i18n (en/fr)
backend/Almafrica.Domain/Entities/Configurations/QualityAssessmentQuestionOptionTranslation.csOption i18n (en/fr)
backend/Almafrica.API/Controllers/QualityAssessmentsController.csREST endpoints for assessments (agent-facing)
backend/Almafrica.API/Controllers/Admin/QualityAssessmentQuestionsController.csAdmin CRUD endpoints for questions
backend/Almafrica.Infrastructure/Services/QualityAssessmentService.csCore business logic: start, complete, edit, query
backend/Almafrica.Infrastructure/Services/QualityAssessmentQuestionService.csQuestion CRUD with validation and soft-delete
backend/Almafrica.Application/Interfaces/IQualityAssessmentService.csService interface (16 methods)
backend/Almafrica.Application/Interfaces/IQualityAssessmentQuestionService.csQuestion service interface

Mobile

FilePurpose
mobile/mon_jardin/lib/presentation/select_farmer_for_assessment/select_farmer_for_assessment_screen.dartFarmer selection screen with search/filter
mobile/mon_jardin/lib/presentation/assessment/start_assessment_screen.dartAssessment initiation: crop, quantity, collection point
mobile/mon_jardin/lib/presentation/assessment/assessment_form_screen.dartDynamic question form with photo capture
mobile/mon_jardin/lib/presentation/warehouse_pending_assessments/warehouse_pending_assessments_screen.dartWarehouse view of pending/completed assessments
mobile/mon_jardin/lib/presentation/providers/assessment_form_provider.dartForm state management: answers, drafts, validation
mobile/mon_jardin/lib/domain/repositories/assessment_repository.dartRepository interface
mobile/mon_jardin/lib/data/repositories/assessment_repository_impl.dartOffline-first repository implementation (24h cache)
mobile/mon_jardin/lib/data/datasources/local/assessment_local_datasource.dartSQLite storage: questions, drafts, pending, completed
mobile/mon_jardin/lib/data/datasources/remote/assessment_remote_datasource.dartDio HTTP client for assessment API
mobile/mon_jardin/lib/data/services/assessment_sync_integration.dartSync coordinator singleton
mobile/mon_jardin/lib/data/services/sync/strategies/assessment_sync_strategy.dartRetry logic, LOCAL_ ID resolution, photo upload
mobile/mon_jardin/lib/data/services/sync/assessment_questions_sync.dartQuestion delta sync helper
mobile/mon_jardin/lib/data/services/assessment_photo_service.dartPhoto upload to DigitalOcean Spaces + attach to assessment