Skip to main content

Appendix B: Entity Relationships

This appendix documents the key entity relationships across the Almafrica system. The backend uses PostgreSQL (via Entity Framework Core) as the source of truth, while the Flutter mobile app maintains a SQLite replica for offline-first operation.


Table of Contents

  1. User and Access
  2. Farmer Domain
  3. Client Domain
  4. Production
  5. Orders and Allocation
  6. Warehouse and Stock
  7. Quality
  8. Campaigns and Surveys
  9. Master Data Entities
  10. PostgreSQL vs SQLite Schema Differences

1. User and Access

These entities manage authentication, authorization, session tracking, and audit.

erDiagram
ApplicationUser ||--o{ UserSession : "has sessions"
ApplicationUser ||--o{ AspNetUserRoles : "has roles"
AspNetUserRoles }o--|| AspNetRole : "assigned role"
AspNetRole ||--o{ RolePermission : "grants"
ApplicationUser ||--o{ PasswordHistory : "password history"
ApplicationUser ||--o{ TwoFactorBackupCode : "2FA codes"
ApplicationUser ||--o{ LoginHistory : "login audit"
ApplicationUser ||--o{ Notification : "receives"
UserGroup ||--o{ UserGroupMembership : "contains"
ApplicationUser ||--o{ UserGroupMembership : "belongs to"
AspNetRole ||--o{ RolePasswordPolicy : "enforces"
RolePasswordPolicy }o--|| PasswordPolicy : "uses"
AspNetRole ||--o{ RoleAuditLog : "audited by"

Entity Details

EntityPKKey Fields
ApplicationUserstring IdEmail, PhoneNumber, FullName (ASP.NET Identity)
UserSessionGuid IdUserId, SessionToken, DeviceType, IpAddress, IsActive
AspNetRolestring IdName, NormalizedName (ASP.NET Identity)
UserGroupGuid IdName, Description
UserGroupMembershipGuid IdUserGroupId, UserId
PasswordPolicyGuid IdMinLength, RequireUppercase, MaxAge, etc.
RolePasswordPolicycompositeRoleId, PasswordPolicyId
RoleAuditLogGuid IdRoleId, Action, PerformedBy, Timestamp
PasswordHistoryGuid IdUserId, PasswordHash, CreatedAt
LoginHistoryGuid IdUserId, IpAddress, UserAgent, LoginTime, Success
TwoFactorBackupCodeGuid IdUserId, Code, IsUsed
NotificationGuid IdUserId, Title, Message, IsRead, Type

2. Farmer Domain

These entities represent field agents, farmers, their crops, crop offers, and field visits.

erDiagram
Agent ||--o{ Farmer : "manages"
Agent ||--o{ Farmer : "created"
Agent ||--o{ FarmerVisit : "performs"
Agent }o--o| Province : "operates in"
Agent }o--o| Territory : "operates in"
Agent }o--o| Village : "operates in"

Farmer ||--o{ FarmerCrop : "grows"
Farmer ||--o{ FarmerCropOffer : "offers"
Farmer ||--o{ FarmerVisit : "visited during"
Farmer ||--o{ Production : "has cycles"
Farmer ||--o{ Harvest : "harvests"
Farmer ||--o{ CropBatch : "delivers"
Farmer ||--o{ Distribution : "receives"
Farmer }o--o| Province : "lives in"
Farmer }o--o| Territory : "in territory"
Farmer }o--o| Village : "in village"
Farmer }o--o| CollectionPoint : "delivers to"

FarmerCrop }o--|| Crop : "crop type"
FarmerCrop ||--o{ Production : "linked to"
FarmerCrop ||--o{ FarmerCropFertilizer : "uses"

FarmerCropOffer }o--|| Crop : "offers crop"
FarmerCropOffer }o--o| CollectionPoint : "delivers at"
FarmerCropOffer ||--o{ FarmerCropOfferPriority : "has priorities"
FarmerCropOffer ||--o{ FarmerCropOfferIssue : "has issues"

FarmerVisit }o--|| Farmer : "visits"
FarmerVisit }o--|| Agent : "by agent"
FarmerVisit ||--o{ Production : "captures"

Entity Details

EntityPKKey Fields
AgentGuid IdAuthUserId, FullName, Email, ProvinceId, TerritoryId, VillageId, IsActive
FarmerGuid IdAuthUserId, FirstName, LastName, PhoneNumber, FarmSize, ManagedById, CreatedByAgentId, ApprovalStatus, Status
FarmerCropGuid IdFarmerId, CropId, VarietyOrLocalName, IsActive
FarmerCropOfferGuid IdFarmerId, CropId, ProductionSource, SupplyFrequency, AvgVolumeValue, ExpectedPriceMin/Max, IsActive
FarmerCropOfferPrioritycompositeFarmerCropOfferId, PurchasePriorityConfigId
FarmerCropOfferIssuecompositeFarmerCropOfferId, CommonIssueConfigId
FarmerVisitGuid IdFarmerId, AgentId, StartedAt, EndedAt, Purpose, Status, Latitude, Longitude

3. Client Domain

These entities represent clients (buyers), their crop demands, and business documents.

erDiagram
Client ||--o{ Order : "places"
Client ||--o{ ClientCropDemand : "demands"
Client ||--o{ ClientDocument : "uploads"
Client }o--o| Province : "located in"
Client }o--o| Territory : "in territory"
Client }o--o| Village : "in village"
Client }o--o| BusinessType : "business type"
Client }o--o| CollectionPoint : "uses"
Client }o--o| PaymentMethod : "pays via"
Client }o--o| Currency : "prefers"
Client }o--o| Agent : "assigned to"

ClientCropDemand }o--|| Crop : "demands crop"
ClientCropDemand }o--|| MeasurementUnit : "volume unit"
ClientCropDemand ||--o{ ClientCropDemandImportCountry : "imports from"
ClientCropDemand ||--o{ ClientCropDemandPurchasePriority : "priorities"
ClientCropDemand ||--o{ ClientCropDemandCommonIssue : "faces issues"

ClientDocument }o--|| Client : "belongs to"

Entity Details

EntityPKKey Fields
ClientGuid IdAuthUserId, CompanyName, ContactPersonName, PhoneNumber, BusinessTypeId, ApprovalStatus, Status
ClientCropDemandGuid IdClientId, CropId, SourcingOrigin, PurchaseFrequency, AvgVolumeValue, UsualBuyPriceMin/Max, IsActive
ClientCropDemandImportCountrycompositeClientCropDemandId, CountryConfigId
ClientCropDemandPurchasePrioritycompositeClientCropDemandId, PurchasePriorityConfigId
ClientCropDemandCommonIssuecompositeClientCropDemandId, CommonIssueConfigId
ClientDocumentGuid IdClientId, DocumentType, DocumentUrl, OriginalFileName, FileSize, MimeType

4. Production

These entities track the agricultural production lifecycle from planting through harvest.

erDiagram
Production }o--|| Farmer : "owned by"
Production }o--|| Crop : "produces"
Production }o--o| CropCategory : "categorized as"
Production }o--o| FarmerCrop : "crop profile"
Production }o--o| FarmerVisit : "captured during"
Production }o--o| MeasurementUnit : "yield unit"
Production ||--o{ Harvest : "yields"
Production ||--o{ ProductionInput : "uses inputs"
Production ||--o{ CropBatch : "delivers to"
Production ||--o{ ProductionAuditEntry : "audit trail"

Harvest }o--|| Production : "from cycle"
Harvest }o--|| Farmer : "by farmer"
Harvest }o--|| Crop : "crop type"
Harvest }o--|| MeasurementUnit : "unit"

ProductionInput }o--|| Production : "for cycle"

Crop }o--|| CropCategory : "belongs to"
Crop ||--o{ CropTranslation : "translations"
CropCategory ||--o{ CropCategoryTranslation : "translations"

Entity Details

EntityPKKey Fields
ProductionGuid IdFarmerId, CropId, CropCategoryId, FarmerCropId, FarmerVisitId, AreaPlanted, PlantingDate, ExpectedHarvestStartDate, EstimatedYield, Status, IsDraft
HarvestGuid IdProductionId, FarmerId, CropId, HarvestDate, Quantity, UnitId, QualityGrade, AvailableQuantity
ProductionInputGuid IdProductionId, InputType, Name, Quantity, ApplicationDate
ProductionAuditEntryGuid IdProductionId, Action, FieldName, OldValue, NewValue, PerformedBy
CropGuid IdName (translatable), CropCategoryId, IsActive
CropCategoryGuid IdName (translatable), Code

5. Orders and Allocation

These entities manage the order lifecycle from placement through delivery and payment.

erDiagram
Order }o--|| Client : "placed by"
Order }o--o| CollectionPoint : "sourced from"
Order }o--o| DeliveryMethod : "delivered via"
Order }o--o| LogisticsPartner : "shipped by"
Order }o--o| Currency : "currency"
Order ||--o{ OrderItem : "contains"
Order ||--o{ Payment : "paid by"
Order ||--o| Distribution : "distributed as"

OrderItem }o--|| Crop : "for crop"
OrderItem }o--o| Harvest : "from harvest"
OrderItem }o--|| MeasurementUnit : "unit"
OrderItem ||--o{ OrderAllocation : "allocated from"
OrderItem ||--o{ StockMovement : "creates movements"

OrderAllocation }o--|| OrderItem : "fulfills"
OrderAllocation }o--|| CropBatch : "sources from"
OrderAllocation ||--o{ StockMovement : "records movement"

Payment }o--|| Order : "pays for"

Distribution }o--|| Order : "delivers"

Entity Details

EntityPKKey Fields
OrderGuid IdOrderNumber, ClientId, OrderDate, Status, TotalAmount, DeliveryMethodId, CollectionPointId, CurrencyId
OrderItemGuid IdOrderId, CropId, HarvestId, Quantity, MeasurementUnitId, UnitPrice, TotalPrice
OrderAllocationGuid IdOrderItemId, CropBatchId, AllocatedQuantity, AllocationDate, AllocatedByUserId
PaymentGuid IdOrderId, Amount, PaymentDate, PaymentMethod, TransactionReference, Status
DistributionGuid IdOrderId, ScheduledDate, ActualDeliveryDate, DeliveryAddress, DriverName, VehicleNumber

6. Warehouse and Stock

These entities track stock inventory, movements, transfers, losses, and investigations.

erDiagram
CropBatch }o--|| Farmer : "delivered by"
CropBatch }o--|| Crop : "crop type"
CropBatch }o--|| CollectionPoint : "stored at"
CropBatch }o--|| MeasurementUnit : "quantity unit"
CropBatch }o--o| Production : "from cycle"
CropBatch ||--o| QualityAssessment : "assessed by"
CropBatch ||--o{ OrderAllocation : "allocated to"
CropBatch ||--o{ StockMovement : "movements"
CropBatch ||--o{ TransferItem : "transferred in"

StockMovement }o--|| CropBatch : "for batch"
StockMovement }o--|| MeasurementUnit : "unit"
StockMovement }o--o| CollectionPoint : "source CP"
StockMovement }o--o| CollectionPoint : "destination CP"
StockMovement }o--o| OrderItem : "for order item"
StockMovement }o--o| OrderAllocation : "for allocation"
StockMovement }o--o| StockMovement : "reverses"

Transfer ||--o{ TransferItem : "contains"
Transfer }o--|| CollectionPoint : "from source"
Transfer }o--|| CollectionPoint : "to destination"
Transfer ||--o| Investigation : "triggers"

TransferItem }o--|| Transfer : "belongs to"
TransferItem }o--|| CropBatch : "batch"
TransferItem }o--|| MeasurementUnit : "unit"

StockLossRecord }o--|| CollectionPoint : "occurred at"
StockLossRecord ||--o{ StockLossItem : "items"

StockLossItem }o--|| CropBatch : "batch affected"

Investigation }o--|| Transfer : "investigates"

StockAdjustmentItem }o--|| CropBatch : "adjusts"

Entity Details

EntityPKKey Fields
CropBatchGuid IdBatchNumber, FarmerId, CropId, CollectionPointId, DeliveryDate, InitialQuantity, AcceptedQuantity, CurrentStockQuantity, Status, AssessmentStatus, BestBeforeDate
StockMovementGuid IdMovementReference, CropBatchId, MovementType, Quantity, StockBefore, StockAfter, SourceCollectionPointId, DestinationCollectionPointId, MovementDate
TransferGuid IdTransferReference, SourceCollectionPointId, DestinationCollectionPointId, Status, InitiatedByUserId, ApprovedByUserId, HasDiscrepancy
TransferItemGuid IdTransferId, CropBatchId, Quantity, ReceivedQuantity, QuantityUnitId
StockLossRecordGuid IdLossReference, CollectionPointId, Category, LossReason, LossDate, TotalQuantityKg, Status
StockLossItemGuid IdStockLossRecordId, CropBatchId, Quantity
StockAdjustmentItemGuid IdCropBatchId, AdjustmentQuantity, Reason
InvestigationGuid IdInvestigationReference, TransferId, Status, ExpectedQuantity, ReceivedQuantity, DiscrepancyQuantity

7. Quality

These entities manage quality assessment checklists and their answers.

erDiagram
QualityAssessment ||--|| CropBatch : "assesses"
QualityAssessment }o--o| ChecklistVersion : "uses checklist"
QualityAssessment ||--o{ QualityAssessmentAnswer : "has answers"

QualityAssessmentAnswer }o--|| QualityAssessment : "for assessment"
QualityAssessmentAnswer }o--|| QualityAssessmentQuestion : "answers question"
QualityAssessmentAnswer }o--o| QualityAssessmentQuestionOption : "selected option"

QualityAssessmentQuestion ||--o{ QualityAssessmentQuestionOption : "has options"
QualityAssessmentQuestion ||--o{ QualityAssessmentQuestionTranslation : "translations"
QualityAssessmentQuestionOption ||--o{ QualityAssessmentQuestionOptionTranslation : "translations"

ChecklistVersion ||--o{ ChecklistVersionTranslation : "translations"

Entity Details

EntityPKKey Fields
QualityAssessmentGuid IdCropBatchId (unique), AssessmentDate, AssessedByUserId, OverallResult, AcceptedQuantity, RejectedQuantity, ChecklistVersionId, plus crop-category-specific QC fields
QualityAssessmentAnswerGuid IdQualityAssessmentId, QuestionId, QuestionType, NumericValue, BooleanValue, SelectedOptionId, DateValue, TextValue
QualityAssessmentQuestionGuid IdCategory, QuestionType, DisplayOrder, IsRequired, IsActive (config entity with translations)
QualityAssessmentQuestionOptionGuid IdQuestionId, Value, DisplayOrder (config entity with translations)
ChecklistVersionGuid IdVersionNumber, EffectiveDate, IsActive (config entity with translations)

8. Campaigns and Surveys

These entities manage survey campaigns, questionnaire versions, responses, and corrections.

erDiagram
Campaign ||--o{ QuestionnaireVersion : "has versions"
Campaign ||--o{ CampaignRun : "has runs"
Campaign ||--o{ SurveyResponse : "collects"

CampaignRun }o--|| Campaign : "belongs to"
CampaignRun }o--|| QuestionnaireVersion : "uses version"
CampaignRun ||--o{ SurveyResponse : "collects"

QuestionnaireVersion }o--|| Campaign : "for campaign"

SurveyResponse }o--|| Campaign : "for campaign"
SurveyResponse }o--|| QuestionnaireVersion : "uses version"
SurveyResponse }o--o| CampaignRun : "in run"
SurveyResponse ||--o{ ResponseCorrection : "corrected by"

ResponseCorrection }o--|| SurveyResponse : "corrects"

Entity Details

EntityPKKey Fields
CampaignGuid IdName, Description, Objective, Status, CreatedBy, PublishedAt, ClosedAt, CurrentRunNumber
QuestionnaireVersionGuid IdCampaignId, VersionNumber, Schema (JSONB), Status, PublishedAt
CampaignRunGuid IdCampaignId, RunNumber, QuestionnaireVersionId, StartedAt, ClosedAt, StartedBy, ClosedBy
SurveyResponseGuid IdCampaignId, QuestionnaireVersionId, CampaignRunId, SubmittedBy, ResponseData (JSONB), GpsLocation (JSONB), QualityStatus, SubmittedAt, SyncedAt, IsDraft
ResponseCorrectionGuid IdResponseId, FieldKey, OldValue (JSONB), NewValue (JSONB), Reason, CorrectedBy, CorrectedAt

9. Master Data Entities

Master data entities are configuration/reference tables that are synced to mobile devices for offline use. Each entity typically has a corresponding *Translation entity for multi-language support (French/English).

Geographic Hierarchy

EntityParentPurpose
Province--Top-level administrative division
TerritoryProvinceSub-division of province
VillageTerritorySmallest geographic unit
CollectionPoint--Physical crop collection/warehouse location

Crop Reference

EntityPurpose
CropCategoryCrop classification (Cereals, Tubers, Fruits, Coffee/Cacao)
CropIndividual crop type

Farmer Profile Configuration (~26 types synced to mobile)

These are the master data tables synced via MasterDataSyncService:

#EntitySQLite TablePurpose
1SeedSourceConfigseed_sourcesWhere farmers source seeds
2FertilizerTypeConfigfertilizer_typesFertilizer types used
3PesticideTypeConfigpesticide_typesPesticide types used
4IrrigationSourceConfigirrigation_sourcesIrrigation methods
5StorageFacilityTypeConfigstorage_facility_typesStorage facility types
6TransportMeansConfigtransport_meansTransport methods to market
7ElectricityReliabilityConfigelectricity_reliabilitiesGrid reliability levels
8FarmToolConfigfarm_toolsFarm equipment types
9SoilTypeConfigsoil_typesSoil classifications
10WaterAccessTypeConfigwater_access_typesWater source types
11MaritalStatusConfigmarital_statusesMarital status options
12EducationLevelConfigeducation_levelsEducation level options
13LandOwnershipTypeConfigland_ownership_typesLand tenure types
14TerrainTypeConfigterrain_typesTerrain classifications
15IncomeSourceTypeConfigincome_sourcesIncome source types
16MonthlyIncomeRangeConfigmonthly_income_rangesIncome bracket ranges
17ElectricitySourceTypeConfigelectricity_sourcesElectricity source types
18FinancialServiceTypeConfigfinancial_service_typesFinancial service access
19CreditProviderTypeConfigclient_credit_provider_typesCredit provider types
20MeasurementUnitmeasurement_units / client_measurement_unitsUnits of measure (kg, ton, etc.)
21Currencyclient_currenciesSupported currencies
22BusinessTypeclient_business_typesClient business classifications
23PaymentMethodclient_payment_methodsPayment method options
24CollectionPointclient_collection_pointsCollection point reference
25DeliveryMethodclient_delivery_methodsDelivery method options
26LogisticsPartnerclient_logistics_partnersLogistics partner reference
27OrderStatusConfigclient_order_statusesOrder status labels
28PurchasePriorityConfigclient_purchase_prioritiesPurchase priority options
29CommonIssueConfigclient_common_issuesCommon trade issues
30CountryConfigclient_countriesCountry reference for imports
31StockOutTypeConfig--Stock out type categories
32LossCategoryConfig--Loss category classifications
33Gender (enum synced as table)gendersGender options
34ContactMethod (enum synced as table)contact_methodsPreferred contact methods

10. PostgreSQL vs SQLite Schema Differences

The mobile SQLite schema is a denormalized, sync-optimized subset of the PostgreSQL backend schema. Key differences:

Structural Differences

AspectPostgreSQL (Backend)SQLite (Mobile)
Primary KeysGuid (UUID)TEXT (UUID stored as string)
Foreign KeysEnforced with ON DELETE/CASCADEDefined but not always enforced
TranslationsSeparate *Translation tables with FKInlined name_en, name_fr columns
JSONB columnsNative JSONB typeTEXT storing JSON string
EnumsC# enum mapped to int/stringTEXT or INTEGER
Value ObjectsEF Core owned types (e.g., Quantity, BatchNumber)Flattened to scalar columns
Soft DeleteIsDeleted, DeletedAt columnsis_deleted column or missing records
Audit columnsCreatedAt, UpdatedAt on base Entity<T>created_at, updated_at (nullable)

Tables Only in SQLite (Sync Infrastructure)

SQLite TablePurpose
sync_queueOffline command queue (entity type, payload, retry count)
sync_metadataLast sync timestamp per entity type
sync_conflictsDetected sync conflicts for resolution
sync_analyticsSync performance metrics
sync_historyHistorical sync operation log
chunked_uploadsChunked upload state (resume support)
chunked_upload_partsIndividual part upload tracking
api_response_cacheCached API responses for offline
stock_batch_cacheCached stock batch data
stock_aggregation_cacheCached stock aggregations
stock_cache_metadataStock cache freshness metadata
center_stock_cachePer-center stock summary cache
campaigns_cacheCached campaign definitions
survey_master_data_cacheCached survey reference data
survey_file_queueQueued survey file uploads
master_data_syncMaster data sync metadata
client_master_data_syncClient master data sync metadata

Tables Only in PostgreSQL (Server-Side)

PostgreSQL EntityPurpose
AspNetUsers / AspNetRoles / AspNetUserRolesASP.NET Identity
UserSessionSession management
LoginHistoryLogin audit trail
PasswordHistoryPassword reuse prevention
PasswordPolicy / RolePasswordPolicyPassword policy enforcement
IpWhitelistIP-based access control
RoleAuditLogRole change audit
PermissionTemplatePermission template definitions
StockMovementFull stock movement ledger
OrderAllocationBatch-to-order traceability
InvestigationTransfer discrepancy investigations
StockAdjustmentItemManual stock adjustments
ResponseCorrectionSurvey response corrections
NotificationPush notification records
All *Translation tablesMulti-language support (30+ tables)

Mobile-Specific Columns

Several mobile SQLite tables include columns that do not exist in PostgreSQL, used for offline workflow:

Column PatternPurpose
sync_statusTracks if record is pending, synced, failed, conflict
local_idTemporary local ID before server assigns UUID
is_draftWhether record is a local draft not yet submitted
retry_countNumber of sync retry attempts
last_errorLast sync error message
offline_createdWhether record was created offline
needs_syncFlag for records requiring push to server

Migration Strategy

  • PostgreSQL: EF Core migrations, versioned in backend/Almafrica.Infrastructure/Migrations/
  • SQLite: Manual migration chain in DatabaseHelper._performMigrations(), versioned from v1 through v33+ with sequential _migrateVxToVy() methods