diff --git a/schema-documentation.md b/schema-documentation.md index 70cea80..05bd71a 100644 --- a/schema-documentation.md +++ b/schema-documentation.md @@ -969,32 +969,89 @@ This comprehensive database schema is designed for a modern agricultural technol ## Training & Certification ### Training -**Purpose**: Farmer education and skill development +**Purpose**: Admin-managed training program system for farmer skill development **Key Features**: -- Course content management -- Difficulty level classification -- Duration tracking -- Category organization +- Administrative training program management +- Course content and curriculum design +- Participant capacity and prerequisites management +- Admin assignment and selection workflows -**Course Management**: -- Structured learning content -- Beginner to advanced levels -- Time-based progression -- Category-based organization +**Administrative Management**: +- Training program creation and maintenance by admins +- Content management with multimedia support +- Participant capacity limits and prerequisite requirements +- Admin-controlled activation and deactivation + +**Course Structure**: +- Structured learning content with modules and assessments +- Difficulty level classification (beginner to advanced) +- Duration tracking and scheduling +- Category-based organization and filtering + +**Assignment System**: +- Admin-driven farmer selection and assignment +- Capacity management and participant limits +- Assignment reason tracking and documentation +- Notification workflow for selected farmers + +**Fields**: +- `id`: Unique training program identifier +- `title`: Training program name +- `description`: Detailed program description +- `content`: Training materials and curriculum +- `category`: Subject area classification +- `duration`: Training duration in minutes +- `level`: Difficulty level (beginner/intermediate/advanced) +- `prerequisites`: Required knowledge or certifications +- `maxParticipants`: Maximum number of participants +- `createdBy`: Admin who created the training program +- `isActive`: Program availability status +- `createdAt`, `updatedAt`: Timestamps + +**Relations**: Connected to farmer training assignments and admin management ### FarmerTraining -**Purpose**: Individual training progress and achievement tracking +**Purpose**: Admin-assigned training participation and progress tracking **Key Features**: -- Enrollment and progress tracking -- Score and completion management -- Certificate generation -- Learning analytics +- Admin-controlled farmer assignment system +- Training progress and completion tracking +- Assignment reason and deadline management +- Notification workflow for assignments + +**Assignment Management**: +- Admin assignment with reason documentation +- Assignment date and deadline tracking +- Notification system for assigned farmers +- Assignment audit trail and history **Progress Tracking**: - Enrollment status and progress percentage -- Score tracking and assessments -- Completion certificates -- Learning path optimization +- Score tracking and assessment results +- Completion certificates and achievements +- Learning analytics and performance metrics + +**Assignment Workflow**: +- Admin selects training program +- Admin filters and selects suitable farmers +- System creates assignments with notifications +- Farmers receive assignment notifications +- Progress tracking through completion + +**Fields**: +- `id`: Unique assignment identifier +- `farmerId`: Assigned farmer reference +- `trainingId`: Training program reference +- `status`: Training status (ENROLLED, IN_PROGRESS, COMPLETED, DROPPED) +- `progress`: Completion percentage (0-100) +- `score`: Assessment score +- `completedAt`: Completion timestamp +- `assignedBy`: Admin who made the assignment +- `assignedAt`: Assignment creation date +- `assignmentReason`: Reason for farmer selection +- `deadline`: Expected completion date +- `createdAt`, `updatedAt`: Timestamps + +**Relations**: Connected to farmers, training programs, and admin management ### Certification **Purpose**: Professional certification and credential management @@ -1002,7 +1059,24 @@ This comprehensive database schema is designed for a modern agricultural technol - Certification program management - Validity period tracking - Requirements documentation -- Industry recognition +- Industry recognition and standards + +**Certification Management**: +- Certification program definition and maintenance +- Validity period and renewal requirements +- Industry standard compliance +- Quality assurance and recognition + +**Fields**: +- `id`: Unique certification identifier +- `name`: Certification name +- `description`: Certification details and requirements +- `validityPeriod`: Certification validity in months +- `requirements`: Qualification requirements +- `isActive`: Certification program status +- `createdAt`, `updatedAt`: Timestamps + +**Relations**: Connected to farmer certifications and training programs ### FarmerCertification **Purpose**: Individual certification status and management @@ -1010,7 +1084,26 @@ This comprehensive database schema is designed for a modern agricultural technol - Certification status tracking - Issue and expiry date management - Certificate number assignment -- Renewal notifications +- Renewal notifications and workflow + +**Certification Tracking**: +- Application and approval workflow +- Certificate generation and numbering +- Expiry date monitoring and renewal alerts +- Certification verification and validation + +**Fields**: +- `id`: Unique certification record identifier +- `farmerId`: Certified farmer reference +- `certificationId`: Certification program reference +- `status`: Certification status (PENDING, APPROVED, REJECTED, EXPIRED) +- `issuedDate`: Certificate issue date +- `expiryDate`: Certificate expiry date +- `certificateNumber`: Unique certificate number +- `notes`: Additional certification notes +- `createdAt`, `updatedAt`: Timestamps + +**Relations**: Connected to farmers and certification programs --- @@ -1158,6 +1251,10 @@ High-volume counters use `BigInt`: - `PestDiseaseType`: PEST, DISEASE, WEED, NUTRIENT_DEFICIENCY, etc. - `SeverityLevel`: LOW, MEDIUM, HIGH, CRITICAL, CATASTROPHIC +**Training & Certification**: +- `TrainingStatus`: ENROLLED, IN_PROGRESS, COMPLETED, DROPPED +- `CertificationStatus`: PENDING, APPROVED, REJECTED, EXPIRED + **Business Operations**: - `ProcurementStatus`: PENDING to COMPLETED (11 stages) - `PaymentMethod`: CASH, BANK_TRANSFER, MOBILE_MONEY, etc. diff --git a/schema.prisma b/schema.prisma deleted file mode 100644 index 330ee2f..0000000 --- a/schema.prisma +++ /dev/null @@ -1,2228 +0,0 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "postgresql" - url = env("DATABASE_URL") -} - -model User { - id String @id @default(cuid()) - email String @unique - phone String? - name String - role UserRole - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmer Farmer? - buyer Buyer? - administrator Administrator? - notifications NotificationRecipient[] - - @@map("users") -} - -model Farmer { - id String @id @default(cuid()) - userId String @unique - farmerCode String @unique - name String - phone String? - email String? - dateOfBirth DateTime? - gender Gender? - maritalStatus MaritalStatus? - spouseName String? - numberOfChildren Int? @default(0) - religionId String? - religion Religion? @relation(fields: [religionId], references: [id]) - nationalityId String? @default("indonesia") - nationality Country? @relation("FarmerNationality", fields: [nationalityId], references: [id]) - address String? - village String? - district String? - province String? - postalCode String? - countryId String? @default("indonesia") - country Country? @relation("FarmerCountry", fields: [countryId], references: [id]) - - // Location data - latitude Float? - longitude Float? - addressGeoJson Json? // GeoJSON point for precise location - - // Identity information - identityType IdentityType? - identityNumber String? - identityExpiry DateTime? - - // Banking information - bankAccount String? - bankName String? - accountHolderName String? - - // Photos and attachments - profilePhotoUrl String? - idCardFrontUrl String? - idCardBackUrl String? - - // Verification status - isVerified Boolean @default(false) - verificationDate DateTime? - verifiedBy String? // admin ID who verified - verificationNotes String? - - // Farming experience - farmingExperience Int? // years of experience - educationLevelId String? - educationLevel EducationLevel? @relation(fields: [educationLevelId], references: [id]) - occupation String? // primary occupation if not full-time farmer - monthlyIncome Decimal? // estimated monthly income - landOwnership LandOwnership? - primaryCrop String? - farmingMethods String[] // organic, conventional, etc. - hasVehicle Boolean? // for transportation - vehicleType String? // motorcycle, car, truck, etc. - hasSmartphone Boolean? @default(true) - internetAccess Boolean? @default(true) - - // Emergency contact - emergencyContactName String? - emergencyContactPhone String? - emergencyContactRelation String? - - // Status and metadata - status FarmerStatus @default(ACTIVE) - notes String? - joinedAt DateTime @default(now()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - farms Farm[] - trainings FarmerTraining[] - certifications FarmerCertification[] - procurements Procurement[] - harvests Harvest[] - attachments FarmerAttachment[] - workers FarmWorker[] - laborRecords LaborRecord[] - laborSchedules LaborSchedule[] - equipment Equipment[] - assets Asset[] - financialRecords FinancialRecord[] - reviews BuyerReview[] - contracts Contract[] - seasonHarvests SeasonHarvest[] - - @@map("farmers") -} - -model Buyer { - id String @id @default(cuid()) - userId String @unique - buyerCode String @unique - name String - company String? - phone String? - email String? - address String? - - // Enhanced buyer details - buyerType BuyerType? - businessLicense String? - taxNumber String? - contactPerson String? - contactPersonPhone String? - paymentTerms Int? // days - creditLimit Decimal? - preferredProducts String[] - qualityRequirements String? - - // Location and logistics - warehouseAddress String? - deliveryPreference String? - operatingRadius Decimal? // km - - // Business details - businessSize BusinessSize? - yearEstablished Int? - annualVolume Decimal? // estimated annual purchase volume - employeeCount Int? - website String? - - // Financial information - bankAccount String? - bankName String? - accountHolderName String? - - // Verification and status - isVerified Boolean @default(false) - verificationDate DateTime? - verifiedBy String? // admin ID who verified - verificationNotes String? - status BuyerStatus @default(ACTIVE) - - // Photos and documents - profilePhotoUrl String? - businessLicenseUrl String? - taxCertificateUrl String? - - // Preferences - preferredPickupDays String[] // Mon, Tue, Wed, etc. - preferredPickupTime String? - minimumOrderQuantity Decimal? - maximumOrderQuantity Decimal? - - notes String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - procurements Procurement[] - reviews BuyerReview[] - contracts Contract[] - - @@map("buyers") -} - -model Administrator { - id String @id @default(cuid()) - userId String @unique - adminCode String @unique - name String - phone String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - - @@map("administrators") -} - -model Farm { - id String @id @default(cuid()) - farmerId String - farmCode String @unique - name String - address String? - village String? - district String? - province String? - postalCode String? - area Decimal? // in hectares - - // Location data - latitude Float? - longitude Float? - boundaries Json? // GeoJSON polygon for farm boundaries - elevation Float? // meters above sea level - - // Farm characteristics - soilType String? - soilPH Float? - waterSource WaterSource? - irrigationType IrrigationType? - slope SlopeType? - climate ClimateType? - - // Infrastructure - hasElectricity Boolean? @default(false) - hasWaterAccess Boolean? @default(false) - hasStorageFacility Boolean? @default(false) - hasProcessingUnit Boolean? @default(false) - accessRoadType RoadType? - - // Photos and documentation - mainPhotoUrl String? - aerialPhotoUrl String? - soilPhotoUrl String? - - // Ownership and legal - ownershipType FarmOwnership? - landCertificateNumber String? - landCertificateUrl String? - - // Agricultural details - establishedYear Int? - totalInvestment Decimal? - annualProduction Decimal? // estimated kg per year - mainCrops String[] // primary crops grown - farmingSystem FarmingSystem? - organicCertified Boolean? @default(false) - - description String? - notes String? - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmer Farmer @relation(fields: [farmerId], references: [id], onDelete: Cascade) - plots Plot[] - harvests Harvest[] - attachments FarmAttachment[] - weatherData WeatherData[] - weatherForecasts WeatherForecast[] - inputs FarmInput[] - inputSchedules InputSchedule[] - laborRecords LaborRecord[] - laborSchedules LaborSchedule[] - equipmentUsage EquipmentUsage[] - pestDiseaseRecords PestDiseaseRecord[] - financialRecords FinancialRecord[] - soilTests SoilTest[] - - @@map("farms") -} - -model Plot { - id String @id @default(cuid()) - farmId String - name String - area Decimal? // in hectares - productId String? - variantId String? - plantedDate DateTime? - boundaries Json? // GeoJSON polygon for plot boundaries - description String? - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farm Farm @relation(fields: [farmId], references: [id], onDelete: Cascade) - product Product? @relation(fields: [productId], references: [id]) - variant ProductVariant? @relation(fields: [variantId], references: [id]) - harvests Harvest[] - weatherData WeatherData[] - weatherForecasts WeatherForecast[] - inputs FarmInput[] - inputSchedules InputSchedule[] - laborRecords LaborRecord[] - laborSchedules LaborSchedule[] - equipmentUsage EquipmentUsage[] - pestDiseaseRecords PestDiseaseRecord[] - financialRecords FinancialRecord[] - soilTests SoilTest[] - seasons PlotSeason[] - - @@map("plots") -} - -model Harvest { - id String @id @default(cuid()) - farmerId String - farmId String - plotId String? - variantId String - quantity Decimal // in kg - harvestDate DateTime - qualityGrade QualityGrade - waterContent Decimal? // percentage - density Decimal? // kg/m3 - notes String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmer Farmer @relation(fields: [farmerId], references: [id]) - farm Farm @relation(fields: [farmId], references: [id]) - plot Plot? @relation(fields: [plotId], references: [id]) - variant ProductVariant @relation(fields: [variantId], references: [id]) - procurement Procurement? - modifiers HarvestModifier[] - - @@map("harvests") -} - -model Procurement { - id String @id @default(cuid()) - procurementCode String @unique - farmerId String - buyerId String? - harvestId String @unique - variantId String - - // Quantity and quality details - quantity Decimal // in kg - qualityGrade QualityGrade - waterContent Decimal? // percentage - density Decimal? // kg/m3 - - // Pricing details - basePrice Decimal // price per kg - premiumRate Decimal @default(0) // percentage - totalPrice Decimal - transportCost Decimal? @default(0) - processingCost Decimal? @default(0) - finalAmount Decimal // total amount to be paid - - // Location and logistics - pickupLocation String? - deliveryLocation String? - pickupDate DateTime? - deliveryDate DateTime? - transportMethod String? // truck, motorcycle, etc. - - // Quality assessment - assessedBy String? // quality assessor ID - assessmentDate DateTime? - assessmentNotes String? - rejectedQuantity Decimal? @default(0) - rejectionReason String? - - // Payment details - paymentMethod PaymentMethod? - paymentReference String? - bankAccount String? - - // Status and tracking - status ProcurementStatus @default(PENDING) - procurementDate DateTime @default(now()) - approvedDate DateTime? - approvedBy String? // admin ID who approved - paymentDate DateTime? - completedDate DateTime? - - // Documentation - contractId String? // link to formal contract - contractNumber String? - invoiceNumber String? - receiptNumber String? - - notes String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmer Farmer @relation(fields: [farmerId], references: [id]) - buyer Buyer? @relation(fields: [buyerId], references: [id]) - harvest Harvest @relation(fields: [harvestId], references: [id]) - variant ProductVariant @relation(fields: [variantId], references: [id]) - contract Contract? @relation(fields: [contractId], references: [id]) - attachments ProcurementAttachment[] - - @@map("procurements") -} - -model HarvestModifier { - id String @id @default(cuid()) - harvestId String - modifierId String - value String // actual value applied - adjustment Decimal? // calculated price adjustment - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - harvest Harvest @relation(fields: [harvestId], references: [id], onDelete: Cascade) - modifier ProductModifier @relation(fields: [modifierId], references: [id]) - - @@unique([harvestId, modifierId]) - @@map("harvest_modifiers") -} - -model FarmerAttachment { - id String @id @default(cuid()) - farmerId String - type AttachmentType - title String - description String? - fileUrl String - fileName String - fileSize Int? // in bytes - mimeType String? - uploadedBy String? // user ID who uploaded - isVerified Boolean @default(false) - verifiedBy String? // admin ID who verified - verifiedAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmer Farmer @relation(fields: [farmerId], references: [id], onDelete: Cascade) - - @@map("farmer_attachments") -} - -model FarmAttachment { - id String @id @default(cuid()) - farmId String - type FarmAttachmentType - title String - description String? - fileUrl String - fileName String - fileSize Int? // in bytes - mimeType String? - uploadedBy String? // user ID who uploaded - takenDate DateTime? // when photo was taken - gpsLocation Json? // GeoJSON point where photo was taken - isVerified Boolean @default(false) - verifiedBy String? // admin ID who verified - verifiedAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farm Farm @relation(fields: [farmId], references: [id], onDelete: Cascade) - - @@map("farm_attachments") -} - -model ProcurementAttachment { - id String @id @default(cuid()) - procurementId String - type ProcurementAttachmentType - title String - description String? - fileUrl String - fileName String - fileSize Int? // in bytes - mimeType String? - uploadedBy String? // user ID who uploaded - takenDate DateTime? // when photo was taken - gpsLocation Json? // GeoJSON point where photo was taken - isVerified Boolean @default(false) - verifiedBy String? // admin ID who verified - verifiedAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - procurement Procurement @relation(fields: [procurementId], references: [id], onDelete: Cascade) - - @@map("procurement_attachments") -} - -model Training { - id String @id @default(cuid()) - title String - description String? - content String? - category String - duration Int? // in minutes - level String? // beginner, intermediate, advanced - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmerTrainings FarmerTraining[] - - @@map("trainings") -} - -model FarmerTraining { - id String @id @default(cuid()) - farmerId String - trainingId String - status TrainingStatus @default(ENROLLED) - progress Int @default(0) // percentage - score Float? - completedAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmer Farmer @relation(fields: [farmerId], references: [id], onDelete: Cascade) - training Training @relation(fields: [trainingId], references: [id], onDelete: Cascade) - - @@unique([farmerId, trainingId]) - @@map("farmer_trainings") -} - -model Certification { - id String @id @default(cuid()) - name String - description String? - validityPeriod Int? // in months - requirements String? - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmerCertifications FarmerCertification[] - - @@map("certifications") -} - -model FarmerCertification { - id String @id @default(cuid()) - farmerId String - certificationId String - status CertificationStatus @default(PENDING) - issuedDate DateTime? - expiryDate DateTime? - certificateNumber String? - notes String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmer Farmer @relation(fields: [farmerId], references: [id], onDelete: Cascade) - certification Certification @relation(fields: [certificationId], references: [id], onDelete: Cascade) - - @@unique([farmerId, certificationId]) - @@map("farmer_certifications") -} - -model Article { - id String @id @default(cuid()) - title String - content String - excerpt String? - category String - tags String[] - author String? - status ArticleStatus @default(DRAFT) - views BigInt @default(0) - publishedAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@map("articles") -} - -model Product { - id String @id @default(cuid()) - name String // e.g., "Pepper" - code String @unique - description String? - category String - unit String @default("kg") // kg, ton, etc - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - plots Plot[] - variants ProductVariant[] - modifiers ProductModifier[] - contracts Contract[] - priceHistory PriceHistory[] - marketPrices MarketPrice[] - marketDemand MarketDemand[] - plotSeasons PlotSeason[] - seasonHarvests SeasonHarvest[] - - @@map("products") -} - -model ProductVariant { - id String @id @default(cuid()) - productId String - name String // e.g., "Black Pepper", "White Pepper" - code String @unique - description String? - basePrice Decimal? // default base price - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - product Product @relation(fields: [productId], references: [id], onDelete: Cascade) - plots Plot[] - harvests Harvest[] - procurements Procurement[] - contracts Contract[] - priceHistory PriceHistory[] - marketPrices MarketPrice[] - marketDemand MarketDemand[] - plotSeasons PlotSeason[] - seasonHarvests SeasonHarvest[] - - @@map("product_variants") -} - -model ProductModifier { - id String @id @default(cuid()) - productId String - name String // e.g., "Water Content", "Density" - code String - description String? - selectionType ModifierSelectionType @default(INPUT) - nominalType NominalType @default(NOMINAL) - options String[] // For dropdown selections - minimum Decimal? - maximum Decimal? - unit String? // %, kg/m3, etc - isRequired Boolean @default(false) - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - product Product @relation(fields: [productId], references: [id], onDelete: Cascade) - rules ProductModifierRule[] - harvestModifiers HarvestModifier[] - - @@unique([productId, code]) - @@map("product_modifiers") -} - -model ProductModifierRule { - id String @id @default(cuid()) - modifierId String - condition RuleCondition - priceAdjustment Decimal // amount to adjust price - value String? // For equals condition - minValue Decimal? // For lessThan or between conditions - maxValue Decimal? // For greaterThan or between conditions - description String? - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - modifier ProductModifier @relation(fields: [modifierId], references: [id], onDelete: Cascade) - - @@map("product_modifier_rules") -} - -model PriceHistory { - id String @id @default(cuid()) - productId String? - variantId String? - qualityGrade QualityGrade - basePrice Decimal - marketPrice Decimal? - premiumRate Decimal @default(0) - effectiveDate DateTime - region String? - notes String? - createdAt DateTime @default(now()) - - // Relations - product Product? @relation(fields: [productId], references: [id]) - variant ProductVariant? @relation(fields: [variantId], references: [id]) - - @@map("price_history") -} - -model WeatherData { - id String @id @default(cuid()) - farmId String? - plotId String? - latitude Float - longitude Float - date DateTime - temperature Float? // celsius - humidity Float? // percentage - rainfall Float? // mm - windSpeed Float? // km/h - windDirection String? // N, NE, E, SE, S, SW, W, NW - pressure Float? // hPa - uvIndex Float? - visibility Float? // km - weatherCondition String? // sunny, cloudy, rainy, etc. - dataSource String? // manual, weather_station, api - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farm Farm? @relation(fields: [farmId], references: [id]) - plot Plot? @relation(fields: [plotId], references: [id]) - - @@map("weather_data") -} - -model WeatherForecast { - id String @id @default(cuid()) - farmId String? - plotId String? - latitude Float - longitude Float - forecastDate DateTime - minTemperature Float? - maxTemperature Float? - humidity Float? - rainfall Float? - windSpeed Float? - weatherCondition String? - confidence Float? // percentage - source String - createdAt DateTime @default(now()) - - // Relations - farm Farm? @relation(fields: [farmId], references: [id]) - plot Plot? @relation(fields: [plotId], references: [id]) - - @@map("weather_forecasts") -} - -model FarmInput { - id String @id @default(cuid()) - farmId String - plotId String? - inputType InputType - productName String - brand String? - quantity Decimal - unit String - cost Decimal - supplier String? - supplierContact String? - batchNumber String? - expiryDate DateTime? - applicationDate DateTime? - applicationMethod String? - applicationRate String? // per hectare or per plant - activeIngredient String? // for pesticides/fertilizers - concentration Decimal? // percentage - notes String? - invoiceNumber String? - receiptUrl String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farm Farm @relation(fields: [farmId], references: [id]) - plot Plot? @relation(fields: [plotId], references: [id]) - - @@map("farm_inputs") -} - -model InputSchedule { - id String @id @default(cuid()) - farmId String - plotId String? - inputType InputType - productName String - scheduledDate DateTime - quantity Decimal - unit String - method String? - status ScheduleStatus @default(PENDING) - appliedDate DateTime? - appliedBy String? - actualQuantity Decimal? - notes String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farm Farm @relation(fields: [farmId], references: [id]) - plot Plot? @relation(fields: [plotId], references: [id]) - - @@map("input_schedules") -} - -model Supplier { - id String @id @default(cuid()) - name String - contactPerson String? - phone String? - email String? - address String? - supplierType SupplierType - paymentTerms String? - deliveryTerms String? - qualityCertifications String[] - isActive Boolean @default(true) - notes String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@map("suppliers") -} - -model FarmWorker { - id String @id @default(cuid()) - farmerId String - workerCode String @unique - name String - phone String? - email String? - address String? - identityNumber String? - role WorkerRole - skillLevel SkillLevel? - dailyWage Decimal? - monthlyWage Decimal? - paymentMethod PaymentMethod? - bankAccount String? - emergencyContact String? - emergencyPhone String? - hireDate DateTime? - contractType ContractType? - contractEnd DateTime? - isActive Boolean @default(true) - notes String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmer Farmer @relation(fields: [farmerId], references: [id]) - laborRecords LaborRecord[] - laborSchedules LaborSchedule[] - - @@map("farm_workers") -} - -model LaborRecord { - id String @id @default(cuid()) - farmerId String - farmId String - workerId String? - plotId String? - workType WorkType - hoursWorked Decimal - wages Decimal - workDate DateTime - startTime DateTime? - endTime DateTime? - description String? - supervisor String? // supervisor name or ID - qualityRating Decimal? // 1-5 rating - weather String? - notes String? - approved Boolean @default(false) - approvedBy String? - approvedDate DateTime? - paymentStatus PaymentStatus @default(PENDING) - paidDate DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmer Farmer @relation(fields: [farmerId], references: [id]) - farm Farm @relation(fields: [farmId], references: [id]) - worker FarmWorker? @relation(fields: [workerId], references: [id]) - plot Plot? @relation(fields: [plotId], references: [id]) - - @@map("labor_records") -} - -model LaborSchedule { - id String @id @default(cuid()) - farmerId String - farmId String - workerId String? - plotId String? - workType WorkType - scheduledDate DateTime - estimatedHours Decimal - estimatedWage Decimal? - status ScheduleStatus @default(PENDING) - assignedBy String? - notes String? - actualRecord String? // reference to LaborRecord ID - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmer Farmer @relation(fields: [farmerId], references: [id]) - farm Farm @relation(fields: [farmId], references: [id]) - worker FarmWorker? @relation(fields: [workerId], references: [id]) - plot Plot? @relation(fields: [plotId], references: [id]) - - @@map("labor_schedules") -} - -model Equipment { - id String @id @default(cuid()) - farmerId String - equipmentCode String @unique - name String - type EquipmentType - brand String? - model String? - serialNumber String? - purchaseDate DateTime? - purchasePrice Decimal? - currentValue Decimal? - condition EquipmentCondition - status EquipmentStatus @default(ACTIVE) - location String? // where equipment is stored - fuelType String? // diesel, petrol, electric, manual - capacity String? // engine capacity, load capacity - powerRating String? // horsepower, wattage - yearManufactured Int? - warranty String? - insurancePolicy String? - insuranceExpiry DateTime? - lastMaintenance DateTime? - nextMaintenance DateTime? - maintenanceCost Decimal? - operatingHours Decimal? // total operating hours - isActive Boolean @default(true) - notes String? - photoUrl String? - manualUrl String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmer Farmer @relation(fields: [farmerId], references: [id]) - maintenanceRecords MaintenanceRecord[] - usageRecords EquipmentUsage[] - - @@map("equipment") -} - -model MaintenanceRecord { - id String @id @default(cuid()) - equipmentId String - maintenanceType MaintenanceType - description String - cost Decimal - serviceProvider String? - serviceDate DateTime - nextServiceDue DateTime? - partsReplaced String[] - laborHours Decimal? - invoiceNumber String? - receiptUrl String? - performedBy String? - notes String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - equipment Equipment @relation(fields: [equipmentId], references: [id]) - - @@map("maintenance_records") -} - -model EquipmentUsage { - id String @id @default(cuid()) - equipmentId String - farmId String? - plotId String? - operatorName String? - usageDate DateTime - startTime DateTime? - endTime DateTime? - hoursUsed Decimal - fuelConsumed Decimal? - workType WorkType? - description String? - meterReading Decimal? // odometer, hour meter reading - notes String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - equipment Equipment @relation(fields: [equipmentId], references: [id]) - farm Farm? @relation(fields: [farmId], references: [id]) - plot Plot? @relation(fields: [plotId], references: [id]) - - @@map("equipment_usage") -} - -model Asset { - id String @id @default(cuid()) - farmerId String - assetCode String @unique - name String - type AssetType - category String? // building, land improvement, infrastructure - description String? - purchaseDate DateTime? - purchasePrice Decimal? - currentValue Decimal? - depreciation Decimal? // annual depreciation rate - condition AssetCondition - location String? - size String? // dimensions, area - material String? // construction material - lifespan Int? // expected lifespan in years - warrantyExpiry DateTime? - insurancePolicy String? - insuranceExpiry DateTime? - photoUrls String[] - documentUrls String[] - isActive Boolean @default(true) - notes String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmer Farmer @relation(fields: [farmerId], references: [id]) - - @@map("assets") -} - -model BuyerReview { - id String @id @default(cuid()) - buyerId String - farmerId String - rating Decimal // 1-5 rating - comment String? - reviewType ReviewType - procurementId String? // reference to procurement if applicable - isAnonymous Boolean @default(false) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - buyer Buyer @relation(fields: [buyerId], references: [id]) - farmer Farmer @relation(fields: [farmerId], references: [id]) - - @@map("buyer_reviews") -} - -model PestDiseaseRecord { - id String @id @default(cuid()) - farmId String - plotId String? - type PestDiseaseType - name String - scientificName String? - severity SeverityLevel - affectedArea Decimal? // percentage or hectares - identifiedDate DateTime - identifiedBy String? // farmer, expert, etc. - symptoms String? - treatmentApplied String? - treatmentDate DateTime? - treatmentCost Decimal? - treatmentMethod String? - preventionTaken String? - resolved Boolean @default(false) - resolvedDate DateTime? - recurrence Boolean @default(false) - photos String[] // photo URLs - notes String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farm Farm @relation(fields: [farmId], references: [id]) - plot Plot? @relation(fields: [plotId], references: [id]) - - @@map("pest_disease_records") -} - -model FinancialRecord { - id String @id @default(cuid()) - farmerId String - farmId String? - plotId String? - type TransactionType - category String - subcategory String? - amount Decimal - currencyId String @default("idr") - currency Currency @relation(fields: [currencyId], references: [id]) - description String - transactionDate DateTime - paymentMethod PaymentMethod? - receiptNumber String? - invoiceNumber String? - referenceNumber String? - taxAmount Decimal? @default(0) - bankAccount String? - payee String? // who received payment - approvedBy String? // admin who approved - status TransactionStatus @default(PENDING) - notes String? - attachmentUrls String[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmer Farmer @relation(fields: [farmerId], references: [id]) - farm Farm? @relation(fields: [farmId], references: [id]) - plot Plot? @relation(fields: [plotId], references: [id]) - - @@map("financial_records") -} - -model SoilTest { - id String @id @default(cuid()) - farmId String - plotId String? - testCode String @unique - testDate DateTime - sampleDepth Decimal? // cm - sampleLocation String? - gpsCoordinates Json? // GeoJSON point - - // Basic soil properties - pH Decimal? - organicMatter Decimal? // percentage - soilTexture String? // clay, sand, loam, etc. - bulkDensity Decimal? // g/cm³ - porosity Decimal? // percentage - - // Nutrients (ppm) - nitrogen Decimal? - phosphorus Decimal? - potassium Decimal? - calcium Decimal? - magnesium Decimal? - sulfur Decimal? - - // Micronutrients (ppm) - zinc Decimal? - iron Decimal? - manganese Decimal? - copper Decimal? - boron Decimal? - - // Other properties - conductivity Decimal? // dS/m - cationExchangeCapacity Decimal? // cmol/kg - baseStaturation Decimal? // percentage - carbonNitrogenRatio Decimal? - - // Analysis details - recommendations String? - testLaboratory String? - technicianName String? - testCost Decimal? - reportUrl String? - - notes String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farm Farm @relation(fields: [farmId], references: [id]) - plot Plot? @relation(fields: [plotId], references: [id]) - - @@map("soil_tests") -} - -model EducationLevel { - id String @id @default(cuid()) - name String @unique - level Int // 0=No formal, 1=Elementary, etc. - description String? - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmers Farmer[] - - @@map("education_levels") -} - -model Currency { - id String @id // ISO currency code like "idr", "usd" - name String @unique - code String @unique // ISO 4217 currency code - symbol String // €, $, Rp, etc. - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - financialRecords FinancialRecord[] - - @@map("currencies") -} - -model Country { - id String @id // ISO country code like "indonesia", "malaysia" - name String @unique - code String @unique // ISO 3166-1 alpha-2 code - region String? - continent String? - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmerNationalities Farmer[] @relation("FarmerNationality") - farmerCountries Farmer[] @relation("FarmerCountry") - - @@map("countries") -} - -model Religion { - id String @id @default(cuid()) - name String @unique - description String? - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmers Farmer[] - - @@map("religions") -} - -model Message { - id String @id @default(cuid()) - senderId String - receiverId String? - groupId String? - subject String? - content String - messageType MessageType - priority Priority @default(NORMAL) - isRead Boolean @default(false) - readAt DateTime? - attachments String[] // file URLs - deliveryStatus MessageStatus @default(SENT) - sentAt DateTime @default(now()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@map("messages") -} - -model MarketPrice { - id String @id @default(cuid()) - productId String - variantId String? - market String - region String - price Decimal - unit String @default("kg") - qualityGrade QualityGrade - priceDate DateTime - source String? // where price data came from - volume Decimal? // trading volume - trend PriceTrend? - verified Boolean @default(false) - verifiedBy String? - notes String? - createdAt DateTime @default(now()) - - // Relations - product Product @relation(fields: [productId], references: [id]) - variant ProductVariant? @relation(fields: [variantId], references: [id]) - - @@map("market_prices") -} - -model Contract { - id String @id @default(cuid()) - contractNumber String @unique - farmerId String - buyerId String - productId String? - variantId String? - - // Contract terms - title String - description String? - contractType AgreementType @default(PURCHASE_AGREEMENT) - - // Pricing and quantity - agreedPrice Decimal // price per unit - minimumQuantity Decimal? - maximumQuantity Decimal? - totalValue Decimal? - unit String @default("kg") - - // Quality specifications - qualityGrade QualityGrade? - qualityRequirements String? - - // Timeline - startDate DateTime - endDate DateTime - deliverySchedule String? // delivery frequency/schedule - - // Payment terms - paymentTerms String? // payment conditions - paymentMethod PaymentMethod? - advancePayment Decimal? @default(0) - advancePercentage Decimal? @default(0) - - // Legal and compliance - terms String? // full terms and conditions - penalties String? // penalty clauses - forcemajeure String? // force majeure clause - governingLaw String? // applicable law - - // Status and tracking - status ContractStatus @default(DRAFT) - signedDate DateTime? - signedByFarmer Boolean @default(false) - signedByBuyer Boolean @default(false) - farmerSignature String? // signature data or URL - buyerSignature String? // signature data or URL - witnessName String? - witnessSignature String? - - // Performance tracking - totalDelivered Decimal? @default(0) - totalPaid Decimal? @default(0) - deliveryCount Int @default(0) - - // Renewal and amendments - renewalDate DateTime? - amendmentCount Int @default(0) - parentContractId String? // for contract renewals - - // Documentation - documentUrl String? // contract document - attachmentUrls String[] // supporting documents - - notes String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - farmer Farmer @relation(fields: [farmerId], references: [id]) - buyer Buyer @relation(fields: [buyerId], references: [id]) - product Product? @relation(fields: [productId], references: [id]) - variant ProductVariant? @relation(fields: [variantId], references: [id]) - parentContract Contract? @relation("ContractRenewal", fields: [parentContractId], references: [id]) - renewalContracts Contract[] @relation("ContractRenewal") - procurements Procurement[] // deliveries under this contract - - @@map("contracts") -} - -model Notification { - id String @id @default(cuid()) - title String - message String - type NotificationType - category NotificationCategory @default(GENERAL) - priority Priority @default(NORMAL) - - // Recipients - recipientId String? // specific user ID - recipientType UserRole? // or broadcast to user type - recipientIds String[] // multiple specific users - - // Targeting - farmerIds String[] // specific farmers - buyerIds String[] // specific buyers - region String? // geographic targeting - productIds String[] // product-specific notifications - - // Content and media - content String? // detailed content/body - imageUrl String? - actionUrl String? // deep link or action URL - actionLabel String? // button text - - // Scheduling - scheduledAt DateTime? // for scheduled notifications - expiresAt DateTime? // expiration date - - // Status tracking - status NotificationStatus @default(PENDING) - sentAt DateTime? - deliveredCount Int @default(0) - readCount Int @default(0) - clickCount Int @default(0) - - // Metadata - source String? // system, admin, automated, etc. - sourceId String? // reference to source entity - tags String[] // for categorization - metadata Json? // additional data - - // Tracking - isRead Boolean @default(false) - readAt DateTime? - isClicked Boolean @default(false) - clickedAt DateTime? - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - recipients NotificationRecipient[] - - @@map("notifications") -} - -model NotificationRecipient { - id String @id @default(cuid()) - notificationId String - userId String - isRead Boolean @default(false) - readAt DateTime? - isClicked Boolean @default(false) - clickedAt DateTime? - isDelivered Boolean @default(false) - deliveredAt DateTime? - createdAt DateTime @default(now()) - - // Relations - notification Notification @relation(fields: [notificationId], references: [id], onDelete: Cascade) - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - - @@unique([notificationId, userId]) - @@map("notification_recipients") -} - -model Season { - id String @id @default(cuid()) - name String // e.g., "Wet Season 2024", "Dry Season 2024" - seasonType SeasonType - year Int - - // Timeline - startDate DateTime - endDate DateTime - - // Weather characteristics - avgRainfall Decimal? // mm - avgTemperature Float? // celsius - avgHumidity Decimal? // percentage - - // Agricultural phases - plantingStart DateTime? - plantingEnd DateTime? - growingStart DateTime? - growingEnd DateTime? - harvestStart DateTime? - harvestEnd DateTime? - - // Region and scope - region String? - province String? - country String @default("Indonesia") - - // Crop recommendations - recommendedCrops String[] // suitable crops for this season - notRecommendedCrops String[] // crops to avoid - - // Market expectations - expectedDemand DemandLevel? - priceOutlook PriceTrend? - marketNotes String? - - // Agricultural activities - activities Json? // structured data for farming activities - - // Status - isActive Boolean @default(true) - isCurrent Boolean @default(false) - - description String? - notes String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - plots PlotSeason[] - harvests SeasonHarvest[] - weatherData SeasonWeather[] - - @@unique([seasonType, year, region]) - @@map("seasons") -} - -model PlotSeason { - id String @id @default(cuid()) - plotId String - seasonId String - productId String? - variantId String? - - // Planting details - plantedDate DateTime? - plantedArea Decimal? // hectares actually planted - seedVariety String? - - // Expected outcomes - expectedYield Decimal? // kg per hectare - expectedHarvest Decimal? // total kg expected - expectedHarvestDate DateTime? - - // Actual outcomes - actualYield Decimal? // kg per hectare - actualHarvest Decimal? // total kg harvested - actualHarvestDate DateTime? - - status PlantingStatus @default(PLANNED) - notes String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - plot Plot @relation(fields: [plotId], references: [id]) - season Season @relation(fields: [seasonId], references: [id]) - product Product? @relation(fields: [productId], references: [id]) - variant ProductVariant? @relation(fields: [variantId], references: [id]) - - @@unique([plotId, seasonId]) - @@map("plot_seasons") -} - -model SeasonHarvest { - id String @id @default(cuid()) - seasonId String - farmerId String - productId String - variantId String? - - totalQuantity Decimal - averageQuality QualityGrade - totalValue Decimal - - harvestCount Int @default(1) - firstHarvest DateTime - lastHarvest DateTime? - - notes String? - createdAt DateTime @default(now()) - - // Relations - season Season @relation(fields: [seasonId], references: [id]) - farmer Farmer @relation(fields: [farmerId], references: [id]) - product Product @relation(fields: [productId], references: [id]) - variant ProductVariant? @relation(fields: [variantId], references: [id]) - - @@map("season_harvests") -} - -model SeasonWeather { - id String @id @default(cuid()) - seasonId String - region String - - // Aggregated weather data - totalRainfall Decimal? // mm - avgTemperature Float? // celsius - minTemperature Float? // celsius - maxTemperature Float? // celsius - avgHumidity Decimal? // percentage - - // Extreme events - droughtDays Int? @default(0) - floodDays Int? @default(0) - stormCount Int? @default(0) - - // Impact assessment - cropDamage Decimal? // percentage - yieldImpact Decimal? // percentage change - - notes String? - createdAt DateTime @default(now()) - - // Relations - season Season @relation(fields: [seasonId], references: [id]) - - @@unique([seasonId, region]) - @@map("season_weather") -} - -model MarketDemand { - id String @id @default(cuid()) - productId String - variantId String? - region String - demandLevel DemandLevel - estimatedVolume Decimal? - priceRange String? - season String? - factors String[] // factors affecting demand - forecastDate DateTime - forecastBy String? - accuracy Decimal? // percentage - notes String? - createdAt DateTime @default(now()) - - // Relations - product Product @relation(fields: [productId], references: [id]) - variant ProductVariant? @relation(fields: [variantId], references: [id]) - - @@map("market_demand") -} - -// Enums -enum UserRole { - FARMER - BUYER - ADMINISTRATOR -} - -enum QualityGrade { - A - B - C -} - -enum ProcurementStatus { - PENDING - QUALITY_ASSESSMENT - APPROVED - REJECTED - IN_TRANSIT - DELIVERED - INVOICED - PAID - COMPLETED - CANCELLED - PARTIALLY_REJECTED -} - -enum TrainingStatus { - ENROLLED - IN_PROGRESS - COMPLETED - DROPPED -} - -enum CertificationStatus { - PENDING - APPROVED - REJECTED - EXPIRED -} - -enum ArticleStatus { - DRAFT - PUBLISHED - ARCHIVED -} - -enum ModifierSelectionType { - DROPDOWN - INPUT -} - -enum NominalType { - NOMINAL - PERCENTAGE -} - -enum RuleCondition { - EQUALS - LESS_THAN - GREATER_THAN - BETWEEN -} - -enum Gender { - MALE - FEMALE - OTHER -} - -enum IdentityType { - KTP // Indonesian ID Card - PASSPORT - DRIVING_LICENSE - OTHER -} - -// EducationLevel enum converted to table above - -enum FarmerStatus { - ACTIVE - INACTIVE - SUSPENDED - PENDING_VERIFICATION - BLACKLISTED -} - -enum AttachmentType { - PROFILE_PHOTO - ID_CARD_FRONT - ID_CARD_BACK - FARMING_LICENSE - LAND_CERTIFICATE - BANK_STATEMENT - CONTRACT - CERTIFICATE - OTHER_DOCUMENT -} - -enum MaritalStatus { - SINGLE - MARRIED - DIVORCED - WIDOWED - SEPARATED -} - -// Religion enum converted to table above - -enum LandOwnership { - OWNER - TENANT - SHARECROPPER - COOPERATIVE_MEMBER - GOVERNMENT_LEASE - FAMILY_LAND - OTHER -} - -enum WaterSource { - RAIN_FED - IRRIGATION_CANAL - WELL - RIVER - POND - GROUNDWATER - SPRING - MIXED -} - -enum IrrigationType { - FLOOD - SPRINKLER - DRIP - FURROW - MANUAL - NONE -} - -enum SlopeType { - FLAT - GENTLE - MODERATE - STEEP - VERY_STEEP -} - -enum ClimateType { - TROPICAL_WET - TROPICAL_DRY - SUBTROPICAL - TEMPERATE - HIGHLAND -} - -enum RoadType { - PAVED - GRAVEL - DIRT - FOOTPATH - NO_ACCESS -} - -enum FarmOwnership { - PRIVATE_OWNED - LEASED - SHARECROPPED - COOPERATIVE - GOVERNMENT - COMMUNAL - FAMILY_INHERITED -} - -enum FarmingSystem { - MONOCULTURE - POLYCULTURE - MIXED_FARMING - ORGANIC - CONVENTIONAL - INTEGRATED - PERMACULTURE - AGROFORESTRY -} - -enum FarmAttachmentType { - MAIN_PHOTO - AERIAL_PHOTO - SOIL_PHOTO - CROP_PHOTO - INFRASTRUCTURE_PHOTO - LAND_CERTIFICATE - SURVEY_MAP - WATER_SOURCE_PHOTO - ENTRANCE_PHOTO - BOUNDARY_PHOTO - EQUIPMENT_PHOTO - STORAGE_PHOTO - OTHER_DOCUMENT -} - -enum PaymentMethod { - CASH - BANK_TRANSFER - MOBILE_MONEY - CHECK - DIGITAL_WALLET - CREDIT - BARTER - INSTALLMENT -} - -enum ProcurementAttachmentType { - PRODUCT_PHOTO - QUALITY_ASSESSMENT_PHOTO - WEIGHING_PHOTO - PACKAGING_PHOTO - LOADING_PHOTO - DELIVERY_PHOTO - CONTRACT_DOCUMENT - INVOICE - RECEIPT - PAYMENT_PROOF - QUALITY_CERTIFICATE - TRANSPORT_DOCUMENT - REJECTION_PHOTO - SIGNATURE_DOCUMENT - OTHER_DOCUMENT -} - -// Input Management Enums -enum InputType { - SEED - FERTILIZER - PESTICIDE - HERBICIDE - FUNGICIDE - INSECTICIDE - EQUIPMENT_RENTAL - FUEL - IRRIGATION_WATER - MULCH - COMPOST - LIME - OTHER -} - -enum ScheduleStatus { - PENDING - SCHEDULED - IN_PROGRESS - COMPLETED - CANCELLED - OVERDUE -} - -enum SupplierType { - SEED_SUPPLIER - FERTILIZER_SUPPLIER - PESTICIDE_SUPPLIER - EQUIPMENT_SUPPLIER - GENERAL_SUPPLIER - COOPERATIVE - GOVERNMENT_AGENCY -} - -// Labor Management Enums -enum WorkerRole { - PERMANENT - SEASONAL - DAILY - CONTRACTOR - SUPERVISOR - FOREMAN - SPECIALIST -} - -enum SkillLevel { - BEGINNER - INTERMEDIATE - ADVANCED - EXPERT -} - -enum ContractType { - PERMANENT - TEMPORARY - SEASONAL - PROJECT_BASED - DAILY -} - -enum WorkType { - PLANTING - WEEDING - FERTILIZING - HARVESTING - IRRIGATION - PEST_CONTROL - SOIL_PREPARATION - PRUNING - GENERAL_MAINTENANCE - EQUIPMENT_OPERATION - PROCESSING - PACKAGING - TRANSPORT -} - -enum PaymentStatus { - PENDING - APPROVED - PAID - OVERDUE - CANCELLED -} - -// Equipment & Asset Enums -enum EquipmentType { - TRACTOR - HARVESTER - PLANTER - CULTIVATOR - IRRIGATION_SYSTEM - SPRAYER - THRESHER - MOWER - TOOLS - VEHICLE - PROCESSING_EQUIPMENT - STORAGE_EQUIPMENT -} - -enum EquipmentCondition { - EXCELLENT - GOOD - FAIR - POOR - NEEDS_REPAIR - OUT_OF_ORDER -} - -enum EquipmentStatus { - ACTIVE - INACTIVE - MAINTENANCE - REPAIR - RETIRED -} - -enum MaintenanceType { - PREVENTIVE - CORRECTIVE - EMERGENCY - OVERHAUL - INSPECTION - CALIBRATION -} - -enum AssetType { - BUILDING - LAND_IMPROVEMENT - INFRASTRUCTURE - VEHICLE - MACHINERY - FURNITURE - TECHNOLOGY - OTHER -} - -enum AssetCondition { - NEW - EXCELLENT - GOOD - FAIR - POOR - DAMAGED -} - -// Buyer Enums -enum BuyerType { - INDIVIDUAL - WHOLESALER - RETAILER - PROCESSOR - EXPORTER - COOPERATIVE - GOVERNMENT - RESTAURANT - HOTEL -} - -enum BusinessSize { - MICRO - SMALL - MEDIUM - LARGE - ENTERPRISE -} - -enum BuyerStatus { - ACTIVE - INACTIVE - SUSPENDED - PENDING_VERIFICATION - BLACKLISTED -} - -enum ReviewType { - PAYMENT_TIMELINESS - QUALITY_REQUIREMENTS - COMMUNICATION - OVERALL_EXPERIENCE - PRICE_FAIRNESS -} - -// Pest & Disease Enums -enum PestDiseaseType { - PEST - DISEASE - WEED - NUTRIENT_DEFICIENCY - VIRUS - FUNGUS - BACTERIA -} - -enum SeverityLevel { - LOW - MEDIUM - HIGH - CRITICAL - CATASTROPHIC -} - -// Financial Enums -enum TransactionType { - INCOME - EXPENSE - INVESTMENT - LOAN - LOAN_PAYMENT - INSURANCE_PAYMENT - TAX_PAYMENT - GRANT - SUBSIDY - REFUND -} - -enum TransactionStatus { - PENDING - APPROVED - COMPLETED - REJECTED - CANCELLED - FAILED -} - -// Notification Enums -enum NotificationType { - SYSTEM - ANNOUNCEMENT - ALERT - REMINDER - PROMOTION - UPDATE - WARNING - INFO - SUCCESS - ERROR -} - -enum NotificationCategory { - GENERAL - PROCUREMENT - PAYMENT - QUALITY - WEATHER - PRICE_ALERT - TRAINING - CERTIFICATION - MAINTENANCE - HARVEST - PLANTING - MARKET - CONTRACT - COMPLIANCE -} - -enum NotificationStatus { - PENDING - SCHEDULED - SENT - DELIVERED - FAILED - CANCELLED - EXPIRED -} - -// Communication Enums -enum MessageType { - PERSONAL - BROADCAST - NOTIFICATION - ALERT - SYSTEM - ANNOUNCEMENT -} - -enum Priority { - LOW - NORMAL - HIGH - URGENT - CRITICAL -} - -enum MessageStatus { - SENT - DELIVERED - READ - FAILED - PENDING -} - -// Contract Enums -enum AgreementType { - PURCHASE_AGREEMENT - SUPPLY_CONTRACT - EXCLUSIVE_SUPPLY - SEASONAL_CONTRACT - FORWARD_CONTRACT - SPOT_CONTRACT - CONSIGNMENT - PARTNERSHIP -} - -enum ContractStatus { - DRAFT - PENDING_REVIEW - PENDING_SIGNATURE - ACTIVE - FULFILLED - EXPIRED - TERMINATED - CANCELLED - BREACH - RENEWED -} - -// Season Enums -enum SeasonType { - WET_SEASON - DRY_SEASON - TRANSITION - YEAR_ROUND - SPRING - SUMMER - FALL - WINTER -} - -enum PlantingStatus { - PLANNED - PLANTED - GROWING - FLOWERING - FRUITING - HARVESTING - HARVESTED - FAILED - ABANDONED -} - -// Market Intelligence Enums -enum PriceTrend { - RISING - FALLING - STABLE - VOLATILE -} - -enum DemandLevel { - VERY_LOW - LOW - MODERATE - HIGH - VERY_HIGH - EXCESSIVE -} diff --git a/schemas/README.md b/schemas/README.md new file mode 100644 index 0000000..60fad6e --- /dev/null +++ b/schemas/README.md @@ -0,0 +1,214 @@ +# Split Schema Structure + +This directory contains the split version of the agricultural platform's Prisma schema, organized by domain for better maintainability and team collaboration. + +## Directory Structure + +``` +schemas/ +├── README.md # This file +├── base.prisma # Generator, datasource, and shared enums +├── user.prisma # User management (User, Farmer, Buyer, Administrator) +├── reference.prisma # Reference data (Country, Currency, Religion, EducationLevel) +├── farm.prisma # Farm management (Farm, Plot, SoilTest, FarmAttachment) +├── product.prisma # Product system (Product, ProductVariant, modifiers) +├── procurement.prisma # Trading (Procurement, Contract, Harvest) +├── season.prisma # Seasonal planning (Season, PlotSeason, analytics) +├── operations.prisma # Operations (Input, Labor, Equipment, Assets) +├── financial.prisma # Financial records and transactions +├── communication.prisma # Notifications and messaging +├── weather.prisma # Weather data and forecasts +├── training.prisma # Training and certification +├── market.prisma # Market intelligence and pricing +├── knowledge.prisma # Articles and knowledge base +├── attachments.prisma # File and attachment management +└── pest-disease.prisma # Pest, disease, and review management +``` + +## Benefits of Split Schema + +### 1. **Better Organization** +- Logical grouping by business domain +- Easier to find specific models +- Reduced cognitive load when working on specific features +- Clear separation of concerns + +### 2. **Team Collaboration** +- Multiple developers can work on different schema files simultaneously +- Reduced merge conflicts +- Clear ownership boundaries +- Focused code reviews + +### 3. **Maintainability** +- Easier to understand and modify specific domains +- Better debugging and troubleshooting +- Cleaner dependency management +- Improved documentation per domain + +### 4. **Performance Benefits** +- Faster schema compilation for individual domains +- Easier to identify bottlenecks +- Better indexing strategy per domain +- Optimized queries per business area + +## How to Use Split Schema + +### Option 1: Replace Original Schema +1. **Backup original schema.prisma**: + ```bash + mv schema.prisma schema.prisma.backup + ``` + +2. **Use split schema directory**: + ```bash + # Prisma will automatically read all .prisma files in the directory + npx prisma generate + npx prisma migrate dev + ``` + +### Option 2: Parallel Development +Keep both structures and choose based on your needs: +- Use original `schema.prisma` for production +- Use `schemas/` directory for development and feature work +- Gradually migrate teams to split structure + +## Domain Responsibilities + +### **Core Domains** +- **user.prisma**: Authentication, user profiles, role management +- **reference.prisma**: Master data (countries, currencies, education levels) +- **farm.prisma**: Farm infrastructure, plots, soil management + +### **Business Domains** +- **product.prisma**: Product catalog, variants, quality modifiers +- **procurement.prisma**: Trading, contracts, harvest management +- **season.prisma**: Agricultural cycles, seasonal planning +- **operations.prisma**: Daily operations, labor, equipment + +### **Support Domains** +- **financial.prisma**: Financial tracking, transactions +- **communication.prisma**: Notifications, messaging +- **weather.prisma**: Climate data, forecasts +- **training.prisma**: Education, certifications + +### **Analytics Domains** +- **market.prisma**: Market intelligence, pricing +- **knowledge.prisma**: Content management +- **attachments.prisma**: File management +- **pest-disease.prisma**: Agricultural health tracking + +## File Size Comparison + +| Structure | File Count | Lines per File | Total Lines | +|-----------|------------|----------------|-------------| +| Original | 1 file | ~1,800 lines | 1,800 | +| Split | 15 files | ~120-300 lines| 1,800 | + +## Cross-Domain Relationships + +The split maintains all relationships between domains: + +```prisma +// farmer.prisma references farm.prisma +model Farmer { + farms Farm[] // → farm.prisma +} + +// farm.prisma references farmer.prisma +model Farm { + farmer Farmer @relation(...) // → user.prisma +} +``` + +## Migration Strategy + +### Phase 1: Parallel Structure +- Keep original schema.prisma +- Create split structure alongside +- Use for new feature development + +### Phase 2: Team Adoption +- Train teams on domain boundaries +- Establish ownership per domain +- Use split structure for reviews + +### Phase 3: Full Migration +- Move production to split structure +- Archive original schema.prisma +- Update CI/CD pipelines + +## Best Practices + +### 1. **Domain Boundaries** +- Keep related models together +- Minimize cross-domain dependencies +- Use clear naming conventions + +### 2. **Shared Elements** +- Keep shared enums in base.prisma +- Document cross-domain relationships +- Maintain consistent data types + +### 3. **Team Workflow** +- Assign domain ownership +- Review changes by domain +- Coordinate cross-domain changes + +### 4. **Development** +- Test schema compilation regularly +- Validate relationships across files +- Monitor performance impact + +## Troubleshooting + +### Common Issues + +1. **Duplicate Models/Enums**: + - Ensure original schema.prisma is not in the same directory + - Check for naming conflicts across files + +2. **Missing Relations**: + - Verify cross-file relationships are properly defined + - Check import paths and model references + +3. **Compilation Errors**: + - Run `npx prisma validate` to check schema integrity + - Verify all referenced models exist across files + +### Validation Commands + +```bash +# Validate entire schema +npx prisma validate + +# Generate client to test compilation +npx prisma generate + +# Check database sync +npx prisma migrate dev --create-only +``` + +## Future Enhancements + +### Potential Improvements +1. **Microservice Extraction**: Easier to identify service boundaries +2. **Database Sharding**: Clear data partitioning strategies +3. **Team Scaling**: Better support for large development teams +4. **Domain-Driven Design**: Enhanced DDD implementation + +### Monitoring +- Track file modification patterns +- Monitor domain interaction frequency +- Identify optimization opportunities + +## Support + +For questions about the split schema structure: +1. Check this README for common issues +2. Review domain documentation in each file +3. Validate schema compilation with Prisma CLI +4. Contact the database team for complex migrations + +--- + +**Note**: This split structure maintains full compatibility with the original schema.prisma. All relationships, constraints, and data types remain identical. \ No newline at end of file diff --git a/schemas/attachments.prisma b/schemas/attachments.prisma new file mode 100644 index 0000000..19903c2 --- /dev/null +++ b/schemas/attachments.prisma @@ -0,0 +1,45 @@ +// Attachment Management Domain +// Contains all attachment and file management models + +// Attachment-specific Enums +enum AttachmentType { + PROFILE_PHOTO + ID_CARD_FRONT + ID_CARD_BACK + FARMING_LICENSE + LAND_CERTIFICATE + BANK_STATEMENT + CONTRACT + CERTIFICATE + OTHER_DOCUMENT +} + +// Attachment Models +model FarmerAttachment { + id String @id @default(cuid()) + farmerId String + type AttachmentType + title String + description String? + fileUrl String + fileName String + fileSize Int? // in bytes + mimeType String? + uploadedBy String? // user ID who uploaded + isVerified Boolean @default(false) + verifiedBy String? // admin ID who verified + verifiedAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmer Farmer @relation(fields: [farmerId], references: [id], onDelete: Cascade) + + @@index([farmerId]) + @@index([type]) + @@index([isVerified]) + @@index([uploadedBy]) + @@index([createdAt]) + @@index([mimeType]) + @@map("farmer_attachments") +} \ No newline at end of file diff --git a/schemas/base.prisma b/schemas/base.prisma new file mode 100644 index 0000000..2783bd3 --- /dev/null +++ b/schemas/base.prisma @@ -0,0 +1,205 @@ +// This is the base Prisma schema file +// Contains generator, datasource, and shared enums + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +// Shared Enums - Used across multiple domains + +// Core User Management Enums +enum UserRole { + FARMER + BUYER + ADMINISTRATOR +} + +enum Gender { + MALE + FEMALE + OTHER +} + +enum MaritalStatus { + SINGLE + MARRIED + DIVORCED + WIDOWED + SEPARATED +} + +enum IdentityType { + KTP // Indonesian ID Card + PASSPORT + DRIVING_LICENSE + OTHER +} + +// Quality and Status Enums +enum QualityGrade { + A + B + C +} + +enum PaymentMethod { + CASH + BANK_TRANSFER + MOBILE_MONEY + CHECK + DIGITAL_WALLET + CREDIT + BARTER + INSTALLMENT +} + +enum PaymentStatus { + PENDING + APPROVED + PAID + OVERDUE + CANCELLED +} + +enum ScheduleStatus { + PENDING + SCHEDULED + IN_PROGRESS + COMPLETED + CANCELLED + OVERDUE +} + +// Geographic and Environmental Enums +enum WaterSource { + RAIN_FED + IRRIGATION_CANAL + WELL + RIVER + POND + GROUNDWATER + SPRING + MIXED +} + +enum IrrigationType { + FLOOD + SPRINKLER + DRIP + FURROW + MANUAL + NONE +} + +enum SlopeType { + FLAT + GENTLE + MODERATE + STEEP + VERY_STEEP +} + +enum ClimateType { + TROPICAL_WET + TROPICAL_DRY + SUBTROPICAL + TEMPERATE + HIGHLAND +} + +enum RoadType { + PAVED + GRAVEL + DIRT + FOOTPATH + NO_ACCESS +} + +// Agricultural Enums +enum WorkType { + PLANTING + WEEDING + FERTILIZING + HARVESTING + IRRIGATION + PEST_CONTROL + SOIL_PREPARATION + PRUNING + GENERAL_MAINTENANCE + EQUIPMENT_OPERATION + PROCESSING + PACKAGING + TRANSPORT +} + +enum InputType { + SEED + FERTILIZER + PESTICIDE + HERBICIDE + FUNGICIDE + INSECTICIDE + EQUIPMENT_RENTAL + FUEL + IRRIGATION_WATER + MULCH + COMPOST + LIME + OTHER +} + +enum SeverityLevel { + LOW + MEDIUM + HIGH + CRITICAL + CATASTROPHIC +} + +// Communication and Priority Enums +enum Priority { + LOW + NORMAL + HIGH + URGENT + CRITICAL +} + +enum MessageType { + PERSONAL + BROADCAST + NOTIFICATION + ALERT + SYSTEM + ANNOUNCEMENT +} + +enum MessageStatus { + SENT + DELIVERED + READ + FAILED + PENDING +} + +// Market Intelligence Enums +enum PriceTrend { + RISING + FALLING + STABLE + VOLATILE +} + +enum DemandLevel { + VERY_LOW + LOW + MODERATE + HIGH + VERY_HIGH + EXCESSIVE +} \ No newline at end of file diff --git a/schemas/communication.prisma b/schemas/communication.prisma new file mode 100644 index 0000000..d53959b --- /dev/null +++ b/schemas/communication.prisma @@ -0,0 +1,160 @@ +// Communication & Notifications Domain +// Contains Notification, Message, and communication models + +// Communication-specific Enums +enum NotificationType { + SYSTEM + ANNOUNCEMENT + ALERT + REMINDER + PROMOTION + UPDATE + WARNING + INFO + SUCCESS + ERROR +} + +enum NotificationCategory { + GENERAL + PROCUREMENT + PAYMENT + QUALITY + WEATHER + PRICE_ALERT + TRAINING + CERTIFICATION + MAINTENANCE + HARVEST + PLANTING + MARKET + CONTRACT + COMPLIANCE +} + +enum NotificationStatus { + PENDING + SCHEDULED + SENT + DELIVERED + FAILED + CANCELLED + EXPIRED +} + +// Communication Models +model Notification { + id String @id @default(cuid()) + title String + message String + type NotificationType + category NotificationCategory @default(GENERAL) + priority Priority @default(NORMAL) + + // Recipients + recipientId String? // specific user ID + recipientType UserRole? // or broadcast to user type + recipientIds String[] // multiple specific users + + // Targeting + farmerIds String[] // specific farmers + buyerIds String[] // specific buyers + region String? // geographic targeting + productIds String[] // product-specific notifications + + // Content and media + content String? // detailed content/body + imageUrl String? + actionUrl String? // deep link or action URL + actionLabel String? // button text + + // Scheduling + scheduledAt DateTime? // for scheduled notifications + expiresAt DateTime? // expiration date + + // Status tracking + status NotificationStatus @default(PENDING) + sentAt DateTime? + deliveredCount Int @default(0) + readCount Int @default(0) + clickCount Int @default(0) + + // Metadata + source String? // system, admin, automated, etc. + sourceId String? // reference to source entity + tags String[] // for categorization + metadata Json? // additional data + + // Tracking + isRead Boolean @default(false) + readAt DateTime? + isClicked Boolean @default(false) + clickedAt DateTime? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + recipients NotificationRecipient[] + + @@index([type]) + @@index([category]) + @@index([priority]) + @@index([status]) + @@index([scheduledAt]) + @@index([recipientType]) + @@index([region]) + @@index([createdAt]) + @@map("notifications") +} + +model NotificationRecipient { + id String @id @default(cuid()) + notificationId String + userId String + isRead Boolean @default(false) + readAt DateTime? + isClicked Boolean @default(false) + clickedAt DateTime? + isDelivered Boolean @default(false) + deliveredAt DateTime? + createdAt DateTime @default(now()) + + // Relations + notification Notification @relation(fields: [notificationId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([notificationId, userId]) + @@index([notificationId]) + @@index([userId]) + @@index([isRead]) + @@index([isDelivered]) + @@index([createdAt]) + @@map("notification_recipients") +} + +model Message { + id String @id @default(cuid()) + senderId String + receiverId String? + groupId String? + subject String? + content String + messageType MessageType + priority Priority @default(NORMAL) + isRead Boolean @default(false) + readAt DateTime? + attachments String[] // file URLs + deliveryStatus MessageStatus @default(SENT) + sentAt DateTime @default(now()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([senderId]) + @@index([receiverId]) + @@index([messageType]) + @@index([isRead]) + @@index([sentAt]) + @@index([deliveryStatus]) + @@map("messages") +} \ No newline at end of file diff --git a/schemas/farm.prisma b/schemas/farm.prisma new file mode 100644 index 0000000..83f045f --- /dev/null +++ b/schemas/farm.prisma @@ -0,0 +1,249 @@ +// Farm Management Domain +// Contains Farm, Plot, and related farming infrastructure models + +// Farm-specific Enums +enum FarmOwnership { + PRIVATE_OWNED + LEASED + SHARECROPPED + COOPERATIVE + GOVERNMENT + COMMUNAL + FAMILY_INHERITED +} + +enum FarmingSystem { + MONOCULTURE + POLYCULTURE + MIXED_FARMING + ORGANIC + CONVENTIONAL + INTEGRATED + PERMACULTURE + AGROFORESTRY +} + +enum FarmAttachmentType { + MAIN_PHOTO + AERIAL_PHOTO + SOIL_PHOTO + CROP_PHOTO + INFRASTRUCTURE_PHOTO + LAND_CERTIFICATE + SURVEY_MAP + WATER_SOURCE_PHOTO + ENTRANCE_PHOTO + BOUNDARY_PHOTO + EQUIPMENT_PHOTO + STORAGE_PHOTO + OTHER_DOCUMENT +} + +// Farm Models +model Farm { + id String @id @default(cuid()) + farmerId String + farmCode String @unique + name String + address String? + village String? + district String? + province String? + postalCode String? + area Decimal? // in hectares + + // Location data + latitude Float? + longitude Float? + boundaries Json? // GeoJSON polygon for farm boundaries + elevation Float? // meters above sea level + + // Farm characteristics + soilType String? + soilPH Float? + waterSource WaterSource? + irrigationType IrrigationType? + slope SlopeType? + climate ClimateType? + + // Infrastructure + hasElectricity Boolean? @default(false) + hasWaterAccess Boolean? @default(false) + hasStorageFacility Boolean? @default(false) + hasProcessingUnit Boolean? @default(false) + accessRoadType RoadType? + + // Photos and documentation + mainPhotoUrl String? + aerialPhotoUrl String? + soilPhotoUrl String? + + // Ownership and legal + ownershipType FarmOwnership? + landCertificateNumber String? + landCertificateUrl String? + + // Agricultural details + establishedYear Int? + totalInvestment Decimal? + annualProduction Decimal? // estimated kg per year + mainCrops String[] // primary crops grown + farmingSystem FarmingSystem? + organicCertified Boolean? @default(false) + + description String? + notes String? + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmer Farmer @relation(fields: [farmerId], references: [id], onDelete: Cascade) + plots Plot[] + harvests Harvest[] + attachments FarmAttachment[] + weatherData WeatherData[] + weatherForecasts WeatherForecast[] + inputs FarmInput[] + inputSchedules InputSchedule[] + laborRecords LaborRecord[] + laborSchedules LaborSchedule[] + equipmentUsage EquipmentUsage[] + pestDiseaseRecords PestDiseaseRecord[] + financialRecords FinancialRecord[] + soilTests SoilTest[] + + @@index([farmCode]) + @@index([farmerId]) + @@index([latitude, longitude]) + @@index([isActive]) + @@index([establishedYear]) + @@map("farms") +} + +model Plot { + id String @id @default(cuid()) + farmId String + name String + area Decimal? // in hectares + productId String? + variantId String? + plantedDate DateTime? + boundaries Json? // GeoJSON polygon for plot boundaries + description String? + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farm Farm @relation(fields: [farmId], references: [id], onDelete: Cascade) + product Product? @relation(fields: [productId], references: [id]) + variant ProductVariant? @relation(fields: [variantId], references: [id]) + harvests Harvest[] + weatherData WeatherData[] + weatherForecasts WeatherForecast[] + inputs FarmInput[] + inputSchedules InputSchedule[] + laborRecords LaborRecord[] + laborSchedules LaborSchedule[] + equipmentUsage EquipmentUsage[] + pestDiseaseRecords PestDiseaseRecord[] + financialRecords FinancialRecord[] + soilTests SoilTest[] + seasons PlotSeason[] + + @@index([farmId]) + @@index([productId, variantId]) + @@index([plantedDate]) + @@index([isActive]) + @@map("plots") +} + +model FarmAttachment { + id String @id @default(cuid()) + farmId String + type FarmAttachmentType + title String + description String? + fileUrl String + fileName String + fileSize Int? // in bytes + mimeType String? + uploadedBy String? // user ID who uploaded + takenDate DateTime? // when photo was taken + gpsLocation Json? // GeoJSON point where photo was taken + isVerified Boolean @default(false) + verifiedBy String? // admin ID who verified + verifiedAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farm Farm @relation(fields: [farmId], references: [id], onDelete: Cascade) + + @@index([farmId]) + @@index([type]) + @@index([isVerified]) + @@index([createdAt]) + @@map("farm_attachments") +} + +model SoilTest { + id String @id @default(cuid()) + farmId String + plotId String? + testCode String @unique + testDate DateTime + sampleDepth Decimal? // cm + sampleLocation String? + gpsCoordinates Json? // GeoJSON point + + // Basic soil properties + pH Decimal? + organicMatter Decimal? // percentage + soilTexture String? // clay, sand, loam, etc. + bulkDensity Decimal? // g/cm³ + porosity Decimal? // percentage + + // Nutrients (ppm) + nitrogen Decimal? + phosphorus Decimal? + potassium Decimal? + calcium Decimal? + magnesium Decimal? + sulfur Decimal? + + // Micronutrients (ppm) + zinc Decimal? + iron Decimal? + manganese Decimal? + copper Decimal? + boron Decimal? + + // Other properties + conductivity Decimal? // dS/m + cationExchangeCapacity Decimal? // cmol/kg + baseStaturation Decimal? // percentage + carbonNitrogenRatio Decimal? + + // Analysis details + recommendations String? + testLaboratory String? + technicianName String? + testCost Decimal? + reportUrl String? + + notes String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farm Farm @relation(fields: [farmId], references: [id]) + plot Plot? @relation(fields: [plotId], references: [id]) + + @@index([testCode]) + @@index([farmId, plotId]) + @@index([testDate]) + @@index([testLaboratory]) + @@map("soil_tests") +} \ No newline at end of file diff --git a/schemas/financial.prisma b/schemas/financial.prisma new file mode 100644 index 0000000..193c9a1 --- /dev/null +++ b/schemas/financial.prisma @@ -0,0 +1,68 @@ +// Financial Management Domain +// Contains financial records, transactions, and currency models + +// Financial-specific Enums +enum TransactionType { + INCOME + EXPENSE + INVESTMENT + LOAN + LOAN_PAYMENT + INSURANCE_PAYMENT + TAX_PAYMENT + GRANT + SUBSIDY + REFUND +} + +enum TransactionStatus { + PENDING + APPROVED + COMPLETED + REJECTED + CANCELLED + FAILED +} + +// Financial Models +model FinancialRecord { + id String @id @default(cuid()) + farmerId String + farmId String? + plotId String? + type TransactionType + category String + subcategory String? + amount Decimal + currencyId String @default("idr") + currency Currency @relation(fields: [currencyId], references: [id]) + description String + transactionDate DateTime + paymentMethod PaymentMethod? + receiptNumber String? + invoiceNumber String? + referenceNumber String? + taxAmount Decimal? @default(0) + bankAccount String? + payee String? // who received payment + approvedBy String? // admin who approved + status TransactionStatus @default(PENDING) + notes String? + attachmentUrls String[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmer Farmer @relation(fields: [farmerId], references: [id]) + farm Farm? @relation(fields: [farmId], references: [id]) + plot Plot? @relation(fields: [plotId], references: [id]) + + @@index([farmerId, farmId]) + @@index([type]) + @@index([transactionDate]) + @@index([status]) + @@index([category]) + @@index([paymentMethod]) + @@index([amount]) + @@map("financial_records") +} \ No newline at end of file diff --git a/schemas/knowledge.prisma b/schemas/knowledge.prisma new file mode 100644 index 0000000..6e826c5 --- /dev/null +++ b/schemas/knowledge.prisma @@ -0,0 +1,33 @@ +// Knowledge Management Domain +// Contains articles, content management, and knowledge base + +// Knowledge-specific Enums +enum ArticleStatus { + DRAFT + PUBLISHED + ARCHIVED +} + +// Knowledge Models +model Article { + id String @id @default(cuid()) + title String + content String + excerpt String? + category String + tags String[] + author String? + status ArticleStatus @default(DRAFT) + views BigInt @default(0) + publishedAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([category]) + @@index([status]) + @@index([publishedAt]) + @@index([author]) + @@index([views]) + @@index([title]) + @@map("articles") +} \ No newline at end of file diff --git a/schemas/market.prisma b/schemas/market.prisma new file mode 100644 index 0000000..2aab051 --- /dev/null +++ b/schemas/market.prisma @@ -0,0 +1,63 @@ +// Market Intelligence Domain +// Contains market prices, demand analysis, and business intelligence + +model MarketPrice { + id String @id @default(cuid()) + productId String + variantId String? + market String + region String + price Decimal + unit String @default("kg") + qualityGrade QualityGrade + priceDate DateTime + source String? // where price data came from + volume Decimal? // trading volume + trend PriceTrend? + verified Boolean @default(false) + verifiedBy String? + notes String? + createdAt DateTime @default(now()) + + // Relations + product Product @relation(fields: [productId], references: [id]) + variant ProductVariant? @relation(fields: [variantId], references: [id]) + + @@index([productId, variantId]) + @@index([market, region]) + @@index([priceDate]) + @@index([qualityGrade]) + @@index([trend]) + @@index([verified]) + @@index([price]) + @@map("market_prices") +} + +model MarketDemand { + id String @id @default(cuid()) + productId String + variantId String? + region String + demandLevel DemandLevel + estimatedVolume Decimal? + priceRange String? + season String? + factors String[] // factors affecting demand + forecastDate DateTime + forecastBy String? + accuracy Decimal? // percentage + notes String? + createdAt DateTime @default(now()) + + // Relations + product Product @relation(fields: [productId], references: [id]) + variant ProductVariant? @relation(fields: [variantId], references: [id]) + + @@index([productId, variantId]) + @@index([region]) + @@index([demandLevel]) + @@index([forecastDate]) + @@index([season]) + @@index([accuracy]) + @@map("market_demand") +} \ No newline at end of file diff --git a/schemas/operations.prisma b/schemas/operations.prisma new file mode 100644 index 0000000..e1f5252 --- /dev/null +++ b/schemas/operations.prisma @@ -0,0 +1,439 @@ +// Operations Management Domain +// Contains Input management, Labor, Equipment, and daily operations + +// Operations-specific Enums +enum SupplierType { + SEED_SUPPLIER + FERTILIZER_SUPPLIER + PESTICIDE_SUPPLIER + EQUIPMENT_SUPPLIER + GENERAL_SUPPLIER + COOPERATIVE + GOVERNMENT_AGENCY +} + +enum WorkerRole { + PERMANENT + SEASONAL + DAILY + CONTRACTOR + SUPERVISOR + FOREMAN + SPECIALIST +} + +enum SkillLevel { + BEGINNER + INTERMEDIATE + ADVANCED + EXPERT +} + +enum ContractType { + PERMANENT + TEMPORARY + SEASONAL + PROJECT_BASED + DAILY +} + +enum EquipmentType { + TRACTOR + HARVESTER + PLANTER + CULTIVATOR + IRRIGATION_SYSTEM + SPRAYER + THRESHER + MOWER + TOOLS + VEHICLE + PROCESSING_EQUIPMENT + STORAGE_EQUIPMENT +} + +enum EquipmentCondition { + EXCELLENT + GOOD + FAIR + POOR + NEEDS_REPAIR + OUT_OF_ORDER +} + +enum EquipmentStatus { + ACTIVE + INACTIVE + MAINTENANCE + REPAIR + RETIRED +} + +enum MaintenanceType { + PREVENTIVE + CORRECTIVE + EMERGENCY + OVERHAUL + INSPECTION + CALIBRATION +} + +enum AssetType { + BUILDING + LAND_IMPROVEMENT + INFRASTRUCTURE + VEHICLE + MACHINERY + FURNITURE + TECHNOLOGY + OTHER +} + +enum AssetCondition { + NEW + EXCELLENT + GOOD + FAIR + POOR + DAMAGED +} + +// Operations Models +model FarmInput { + id String @id @default(cuid()) + farmId String + plotId String? + inputType InputType + productName String + brand String? + quantity Decimal + unit String + cost Decimal + supplier String? + supplierContact String? + batchNumber String? + expiryDate DateTime? + applicationDate DateTime? + applicationMethod String? + applicationRate String? // per hectare or per plant + activeIngredient String? // for pesticides/fertilizers + concentration Decimal? // percentage + notes String? + invoiceNumber String? + receiptUrl String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farm Farm @relation(fields: [farmId], references: [id]) + plot Plot? @relation(fields: [plotId], references: [id]) + + @@index([farmId, plotId]) + @@index([inputType]) + @@index([applicationDate]) + @@index([supplier]) + @@index([expiryDate]) + @@map("farm_inputs") +} + +model InputSchedule { + id String @id @default(cuid()) + farmId String + plotId String? + inputType InputType + productName String + scheduledDate DateTime + quantity Decimal + unit String + method String? + status ScheduleStatus @default(PENDING) + appliedDate DateTime? + appliedBy String? + actualQuantity Decimal? + notes String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farm Farm @relation(fields: [farmId], references: [id]) + plot Plot? @relation(fields: [plotId], references: [id]) + + @@index([farmId, plotId]) + @@index([scheduledDate]) + @@index([status]) + @@index([inputType]) + @@map("input_schedules") +} + +model Supplier { + id String @id @default(cuid()) + name String + contactPerson String? + phone String? + email String? + address String? + supplierType SupplierType + paymentTerms String? + deliveryTerms String? + qualityCertifications String[] + isActive Boolean @default(true) + notes String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([supplierType]) + @@index([isActive]) + @@index([name]) + @@map("suppliers") +} + +model FarmWorker { + id String @id @default(cuid()) + farmerId String + workerCode String @unique + name String + phone String? + email String? + address String? + identityNumber String? + role WorkerRole + skillLevel SkillLevel? + dailyWage Decimal? + monthlyWage Decimal? + paymentMethod PaymentMethod? + bankAccount String? + emergencyContact String? + emergencyPhone String? + hireDate DateTime? + contractType ContractType? + contractEnd DateTime? + isActive Boolean @default(true) + notes String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmer Farmer @relation(fields: [farmerId], references: [id]) + laborRecords LaborRecord[] + laborSchedules LaborSchedule[] + + @@index([workerCode]) + @@index([farmerId]) + @@index([role]) + @@index([isActive]) + @@index([contractType]) + @@map("farm_workers") +} + +model LaborRecord { + id String @id @default(cuid()) + farmerId String + farmId String + workerId String? + plotId String? + workType WorkType + hoursWorked Decimal + wages Decimal + workDate DateTime + startTime DateTime? + endTime DateTime? + description String? + supervisor String? // supervisor name or ID + qualityRating Decimal? // 1-5 rating + weather String? + notes String? + approved Boolean @default(false) + approvedBy String? + approvedDate DateTime? + paymentStatus PaymentStatus @default(PENDING) + paidDate DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmer Farmer @relation(fields: [farmerId], references: [id]) + farm Farm @relation(fields: [farmId], references: [id]) + worker FarmWorker? @relation(fields: [workerId], references: [id]) + plot Plot? @relation(fields: [plotId], references: [id]) + + @@index([farmerId, farmId]) + @@index([workDate]) + @@index([workType]) + @@index([paymentStatus]) + @@index([workerId]) + @@map("labor_records") +} + +model LaborSchedule { + id String @id @default(cuid()) + farmerId String + farmId String + workerId String? + plotId String? + workType WorkType + scheduledDate DateTime + estimatedHours Decimal + estimatedWage Decimal? + status ScheduleStatus @default(PENDING) + assignedBy String? + notes String? + actualRecord String? // reference to LaborRecord ID + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmer Farmer @relation(fields: [farmerId], references: [id]) + farm Farm @relation(fields: [farmId], references: [id]) + worker FarmWorker? @relation(fields: [workerId], references: [id]) + plot Plot? @relation(fields: [plotId], references: [id]) + + @@index([farmerId, farmId]) + @@index([scheduledDate]) + @@index([status]) + @@index([workType]) + @@index([workerId]) + @@map("labor_schedules") +} + +model Equipment { + id String @id @default(cuid()) + farmerId String + equipmentCode String @unique + name String + type EquipmentType + brand String? + model String? + serialNumber String? + purchaseDate DateTime? + purchasePrice Decimal? + currentValue Decimal? + condition EquipmentCondition + status EquipmentStatus @default(ACTIVE) + location String? // where equipment is stored + fuelType String? // diesel, petrol, electric, manual + capacity String? // engine capacity, load capacity + powerRating String? // horsepower, wattage + yearManufactured Int? + warranty String? + insurancePolicy String? + insuranceExpiry DateTime? + lastMaintenance DateTime? + nextMaintenance DateTime? + maintenanceCost Decimal? + operatingHours Decimal? // total operating hours + isActive Boolean @default(true) + notes String? + photoUrl String? + manualUrl String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmer Farmer @relation(fields: [farmerId], references: [id]) + maintenanceRecords MaintenanceRecord[] + usageRecords EquipmentUsage[] + + @@index([equipmentCode]) + @@index([farmerId]) + @@index([type]) + @@index([status]) + @@index([nextMaintenance]) + @@index([isActive]) + @@map("equipment") +} + +model MaintenanceRecord { + id String @id @default(cuid()) + equipmentId String + maintenanceType MaintenanceType + description String + cost Decimal + serviceProvider String? + serviceDate DateTime + nextServiceDue DateTime? + partsReplaced String[] + laborHours Decimal? + invoiceNumber String? + receiptUrl String? + performedBy String? + notes String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + equipment Equipment @relation(fields: [equipmentId], references: [id]) + + @@index([equipmentId]) + @@index([serviceDate]) + @@index([maintenanceType]) + @@index([nextServiceDue]) + @@map("maintenance_records") +} + +model EquipmentUsage { + id String @id @default(cuid()) + equipmentId String + farmId String? + plotId String? + operatorName String? + usageDate DateTime + startTime DateTime? + endTime DateTime? + hoursUsed Decimal + fuelConsumed Decimal? + workType WorkType? + description String? + meterReading Decimal? // odometer, hour meter reading + notes String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + equipment Equipment @relation(fields: [equipmentId], references: [id]) + farm Farm? @relation(fields: [farmId], references: [id]) + plot Plot? @relation(fields: [plotId], references: [id]) + + @@index([equipmentId]) + @@index([usageDate]) + @@index([farmId, plotId]) + @@index([workType]) + @@map("equipment_usage") +} + +model Asset { + id String @id @default(cuid()) + farmerId String + assetCode String @unique + name String + type AssetType + category String? // building, land improvement, infrastructure + description String? + purchaseDate DateTime? + purchasePrice Decimal? + currentValue Decimal? + depreciation Decimal? // annual depreciation rate + condition AssetCondition + location String? + size String? // dimensions, area + material String? // construction material + lifespan Int? // expected lifespan in years + warrantyExpiry DateTime? + insurancePolicy String? + insuranceExpiry DateTime? + photoUrls String[] + documentUrls String[] + isActive Boolean @default(true) + notes String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmer Farmer @relation(fields: [farmerId], references: [id]) + + @@index([assetCode]) + @@index([farmerId]) + @@index([type]) + @@index([condition]) + @@index([isActive]) + @@map("assets") +} \ No newline at end of file diff --git a/schemas/pest-disease.prisma b/schemas/pest-disease.prisma new file mode 100644 index 0000000..ef22e0c --- /dev/null +++ b/schemas/pest-disease.prisma @@ -0,0 +1,86 @@ +// Pest & Disease Management Domain +// Contains pest, disease, and agricultural health tracking + +// Pest & Disease Enums +enum PestDiseaseType { + PEST + DISEASE + WEED + NUTRIENT_DEFICIENCY + VIRUS + FUNGUS + BACTERIA +} + +enum ReviewType { + PAYMENT_TIMELINESS + QUALITY_REQUIREMENTS + COMMUNICATION + OVERALL_EXPERIENCE + PRICE_FAIRNESS +} + +// Pest & Disease Models +model PestDiseaseRecord { + id String @id @default(cuid()) + farmId String + plotId String? + type PestDiseaseType + name String + scientificName String? + severity SeverityLevel + affectedArea Decimal? // percentage or hectares + identifiedDate DateTime + identifiedBy String? // farmer, expert, etc. + symptoms String? + treatmentApplied String? + treatmentDate DateTime? + treatmentCost Decimal? + treatmentMethod String? + preventionTaken String? + resolved Boolean @default(false) + resolvedDate DateTime? + recurrence Boolean @default(false) + photos String[] // photo URLs + notes String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farm Farm @relation(fields: [farmId], references: [id]) + plot Plot? @relation(fields: [plotId], references: [id]) + + @@index([farmId, plotId]) + @@index([type]) + @@index([severity]) + @@index([identifiedDate]) + @@index([resolved]) + @@index([name]) + @@index([recurrence]) + @@map("pest_disease_records") +} + +model BuyerReview { + id String @id @default(cuid()) + buyerId String + farmerId String + rating Decimal // 1-5 rating + comment String? + reviewType ReviewType + procurementId String? // reference to procurement if applicable + isAnonymous Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + buyer Buyer @relation(fields: [buyerId], references: [id]) + farmer Farmer @relation(fields: [farmerId], references: [id]) + + @@index([buyerId]) + @@index([farmerId]) + @@index([reviewType]) + @@index([rating]) + @@index([procurementId]) + @@index([createdAt]) + @@map("buyer_reviews") +} \ No newline at end of file diff --git a/schemas/procurement.prisma b/schemas/procurement.prisma new file mode 100644 index 0000000..2948c4f --- /dev/null +++ b/schemas/procurement.prisma @@ -0,0 +1,284 @@ +// Procurement & Trading Domain +// Contains Procurement, Contract, Harvest, and related trading models + +// Procurement-specific Enums +enum ProcurementStatus { + PENDING + QUALITY_ASSESSMENT + APPROVED + REJECTED + IN_TRANSIT + DELIVERED + INVOICED + PAID + COMPLETED + CANCELLED + PARTIALLY_REJECTED +} + +enum AgreementType { + PURCHASE_AGREEMENT + SUPPLY_CONTRACT + EXCLUSIVE_SUPPLY + SEASONAL_CONTRACT + FORWARD_CONTRACT + SPOT_CONTRACT + CONSIGNMENT + PARTNERSHIP +} + +enum ContractStatus { + DRAFT + PENDING_REVIEW + PENDING_SIGNATURE + ACTIVE + FULFILLED + EXPIRED + TERMINATED + CANCELLED + BREACH + RENEWED +} + +enum ProcurementAttachmentType { + PRODUCT_PHOTO + QUALITY_ASSESSMENT_PHOTO + WEIGHING_PHOTO + PACKAGING_PHOTO + LOADING_PHOTO + DELIVERY_PHOTO + CONTRACT_DOCUMENT + INVOICE + RECEIPT + PAYMENT_PROOF + QUALITY_CERTIFICATE + TRANSPORT_DOCUMENT + REJECTION_PHOTO + SIGNATURE_DOCUMENT + OTHER_DOCUMENT +} + +// Procurement Models +model Harvest { + id String @id @default(cuid()) + farmerId String + farmId String + plotId String? + variantId String + quantity Decimal // in kg + harvestDate DateTime + qualityGrade QualityGrade + waterContent Decimal? // percentage + density Decimal? // kg/m3 + notes String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmer Farmer @relation(fields: [farmerId], references: [id]) + farm Farm @relation(fields: [farmId], references: [id]) + plot Plot? @relation(fields: [plotId], references: [id]) + variant ProductVariant @relation(fields: [variantId], references: [id]) + procurement Procurement? + modifiers HarvestModifier[] + + @@index([farmerId, farmId]) + @@index([harvestDate]) + @@index([variantId]) + @@index([qualityGrade]) + @@index([plotId]) + @@map("harvests") +} + +model Procurement { + id String @id @default(cuid()) + procurementCode String @unique + farmerId String + buyerId String? + harvestId String @unique + variantId String + + // Quantity and quality details + quantity Decimal // in kg + qualityGrade QualityGrade + waterContent Decimal? // percentage + density Decimal? // kg/m3 + + // Pricing details + basePrice Decimal // price per kg + premiumRate Decimal @default(0) // percentage + totalPrice Decimal + transportCost Decimal? @default(0) + processingCost Decimal? @default(0) + finalAmount Decimal // total amount to be paid + + // Location and logistics + pickupLocation String? + deliveryLocation String? + pickupDate DateTime? + deliveryDate DateTime? + transportMethod String? // truck, motorcycle, etc. + + // Quality assessment + assessedBy String? // quality assessor ID + assessmentDate DateTime? + assessmentNotes String? + rejectedQuantity Decimal? @default(0) + rejectionReason String? + + // Payment details + paymentMethod PaymentMethod? + paymentReference String? + bankAccount String? + + // Status and tracking + status ProcurementStatus @default(PENDING) + procurementDate DateTime @default(now()) + approvedDate DateTime? + approvedBy String? // admin ID who approved + paymentDate DateTime? + completedDate DateTime? + + // Documentation + contractId String? // link to formal contract + contractNumber String? + invoiceNumber String? + receiptNumber String? + + notes String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmer Farmer @relation(fields: [farmerId], references: [id]) + buyer Buyer? @relation(fields: [buyerId], references: [id]) + harvest Harvest @relation(fields: [harvestId], references: [id]) + variant ProductVariant @relation(fields: [variantId], references: [id]) + contract Contract? @relation(fields: [contractId], references: [id]) + attachments ProcurementAttachment[] + + @@index([procurementCode]) + @@index([farmerId, buyerId]) + @@index([status]) + @@index([procurementDate]) + @@index([variantId]) + @@index([qualityGrade]) + @@index([paymentMethod]) + @@map("procurements") +} + +model Contract { + id String @id @default(cuid()) + contractNumber String @unique + farmerId String + buyerId String + productId String? + variantId String? + + // Contract terms + title String + description String? + contractType AgreementType @default(PURCHASE_AGREEMENT) + + // Pricing and quantity + agreedPrice Decimal // price per unit + minimumQuantity Decimal? + maximumQuantity Decimal? + totalValue Decimal? + unit String @default("kg") + + // Quality specifications + qualityGrade QualityGrade? + qualityRequirements String? + + // Timeline + startDate DateTime + endDate DateTime + deliverySchedule String? // delivery frequency/schedule + + // Payment terms + paymentTerms String? // payment conditions + paymentMethod PaymentMethod? + advancePayment Decimal? @default(0) + advancePercentage Decimal? @default(0) + + // Legal and compliance + terms String? // full terms and conditions + penalties String? // penalty clauses + forcemajeure String? // force majeure clause + governingLaw String? // applicable law + + // Status and tracking + status ContractStatus @default(DRAFT) + signedDate DateTime? + signedByFarmer Boolean @default(false) + signedByBuyer Boolean @default(false) + farmerSignature String? // signature data or URL + buyerSignature String? // signature data or URL + witnessName String? + witnessSignature String? + + // Performance tracking + totalDelivered Decimal? @default(0) + totalPaid Decimal? @default(0) + deliveryCount Int @default(0) + + // Renewal and amendments + renewalDate DateTime? + amendmentCount Int @default(0) + parentContractId String? // for contract renewals + + // Documentation + documentUrl String? // contract document + attachmentUrls String[] // supporting documents + + notes String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmer Farmer @relation(fields: [farmerId], references: [id]) + buyer Buyer @relation(fields: [buyerId], references: [id]) + product Product? @relation(fields: [productId], references: [id]) + variant ProductVariant? @relation(fields: [variantId], references: [id]) + parentContract Contract? @relation("ContractRenewal", fields: [parentContractId], references: [id]) + renewalContracts Contract[] @relation("ContractRenewal") + procurements Procurement[] // deliveries under this contract + + @@index([contractNumber]) + @@index([farmerId, buyerId]) + @@index([status]) + @@index([startDate, endDate]) + @@index([productId, variantId]) + @@index([signedDate]) + @@map("contracts") +} + +model ProcurementAttachment { + id String @id @default(cuid()) + procurementId String + type ProcurementAttachmentType + title String + description String? + fileUrl String + fileName String + fileSize Int? // in bytes + mimeType String? + uploadedBy String? // user ID who uploaded + takenDate DateTime? // when photo was taken + gpsLocation Json? // GeoJSON point where photo was taken + isVerified Boolean @default(false) + verifiedBy String? // admin ID who verified + verifiedAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + procurement Procurement @relation(fields: [procurementId], references: [id], onDelete: Cascade) + + @@index([procurementId]) + @@index([type]) + @@index([isVerified]) + @@index([createdAt]) + @@map("procurement_attachments") +} \ No newline at end of file diff --git a/schemas/product.prisma b/schemas/product.prisma new file mode 100644 index 0000000..73743be --- /dev/null +++ b/schemas/product.prisma @@ -0,0 +1,169 @@ +// Product Management Domain +// Contains Product, ProductVariant, modifiers, and pricing models + +// Product-specific Enums +enum ModifierSelectionType { + DROPDOWN + INPUT +} + +enum NominalType { + NOMINAL + PERCENTAGE +} + +enum RuleCondition { + EQUALS + LESS_THAN + GREATER_THAN + BETWEEN +} + +// Product Models +model Product { + id String @id @default(cuid()) + name String // e.g., "Pepper" + code String @unique + description String? + category String + unit String @default("kg") // kg, ton, etc + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + plots Plot[] + variants ProductVariant[] + modifiers ProductModifier[] + contracts Contract[] + priceHistory PriceHistory[] + marketPrices MarketPrice[] + marketDemand MarketDemand[] + plotSeasons PlotSeason[] + seasonHarvests SeasonHarvest[] + + @@index([code]) + @@index([category]) + @@index([isActive]) + @@index([name]) + @@map("products") +} + +model ProductVariant { + id String @id @default(cuid()) + productId String + name String // e.g., "Black Pepper", "White Pepper" + code String @unique + description String? + basePrice Decimal? // default base price + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + product Product @relation(fields: [productId], references: [id], onDelete: Cascade) + plots Plot[] + harvests Harvest[] + procurements Procurement[] + contracts Contract[] + priceHistory PriceHistory[] + marketPrices MarketPrice[] + marketDemand MarketDemand[] + plotSeasons PlotSeason[] + seasonHarvests SeasonHarvest[] + + @@index([code]) + @@index([productId]) + @@index([isActive]) + @@index([basePrice]) + @@map("product_variants") +} + +model ProductModifier { + id String @id @default(cuid()) + productId String + name String // e.g., "Water Content", "Density" + code String + description String? + selectionType ModifierSelectionType @default(INPUT) + nominalType NominalType @default(NOMINAL) + options String[] // For dropdown selections + minimum Decimal? + maximum Decimal? + unit String? // %, kg/m3, etc + isRequired Boolean @default(false) + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + product Product @relation(fields: [productId], references: [id], onDelete: Cascade) + rules ProductModifierRule[] + harvestModifiers HarvestModifier[] + + @@unique([productId, code]) + @@index([productId]) + @@index([isActive]) + @@index([selectionType]) + @@map("product_modifiers") +} + +model ProductModifierRule { + id String @id @default(cuid()) + modifierId String + condition RuleCondition + priceAdjustment Decimal // amount to adjust price + value String? // For equals condition + minValue Decimal? // For lessThan or between conditions + maxValue Decimal? // For greaterThan or between conditions + description String? + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + modifier ProductModifier @relation(fields: [modifierId], references: [id], onDelete: Cascade) + + @@map("product_modifier_rules") +} + +model HarvestModifier { + id String @id @default(cuid()) + harvestId String + modifierId String + value String // actual value applied + adjustment Decimal? // calculated price adjustment + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + harvest Harvest @relation(fields: [harvestId], references: [id], onDelete: Cascade) + modifier ProductModifier @relation(fields: [modifierId], references: [id]) + + @@unique([harvestId, modifierId]) + @@map("harvest_modifiers") +} + +model PriceHistory { + id String @id @default(cuid()) + productId String? + variantId String? + qualityGrade QualityGrade + basePrice Decimal + marketPrice Decimal? + premiumRate Decimal @default(0) + effectiveDate DateTime + region String? + notes String? + createdAt DateTime @default(now()) + + // Relations + product Product? @relation(fields: [productId], references: [id]) + variant ProductVariant? @relation(fields: [variantId], references: [id]) + + @@index([productId, variantId]) + @@index([effectiveDate]) + @@index([qualityGrade]) + @@index([region]) + @@map("price_history") +} \ No newline at end of file diff --git a/schemas/reference.prisma b/schemas/reference.prisma new file mode 100644 index 0000000..9d65690 --- /dev/null +++ b/schemas/reference.prisma @@ -0,0 +1,76 @@ +// Reference Data Domain +// Contains normalized reference tables for countries, currencies, education, religion + +model EducationLevel { + id String @id @default(cuid()) + name String @unique + level Int // 0=No formal, 1=Elementary, etc. + description String? + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmers Farmer[] + + @@index([name]) + @@index([level]) + @@index([isActive]) + @@map("education_levels") +} + +model Currency { + id String @id // ISO currency code like "idr", "usd" + name String @unique + code String @unique // ISO 4217 currency code + symbol String // ₹, $, Rp, etc. + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + financialRecords FinancialRecord[] + + @@index([code]) + @@index([isActive]) + @@index([name]) + @@map("currencies") +} + +model Country { + id String @id // ISO country code like "indonesia", "malaysia" + name String @unique + code String @unique // ISO 3166-1 alpha-2 code + region String? + continent String? + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmerNationalities Farmer[] @relation("FarmerNationality") + farmerCountries Farmer[] @relation("FarmerCountry") + + @@index([code]) + @@index([region]) + @@index([continent]) + @@index([isActive]) + @@index([name]) + @@map("countries") +} + +model Religion { + id String @id @default(cuid()) + name String @unique + description String? + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmers Farmer[] + + @@index([name]) + @@index([isActive]) + @@map("religions") +} \ No newline at end of file diff --git a/schemas/season.prisma b/schemas/season.prisma new file mode 100644 index 0000000..a1b568b --- /dev/null +++ b/schemas/season.prisma @@ -0,0 +1,196 @@ +// Season & Cycle Management Domain +// Contains Season planning, PlotSeason tracking, and seasonal analytics + +// Season-specific Enums +enum SeasonType { + WET_SEASON + DRY_SEASON + TRANSITION + YEAR_ROUND + SPRING + SUMMER + FALL + WINTER +} + +enum PlantingStatus { + PLANNED + PLANTED + GROWING + FLOWERING + FRUITING + HARVESTING + HARVESTED + FAILED + ABANDONED +} + +// Season Models +model Season { + id String @id @default(cuid()) + name String // e.g., "Wet Season 2024", "Dry Season 2024" + seasonType SeasonType + year Int + + // Timeline + startDate DateTime + endDate DateTime + + // Weather characteristics + avgRainfall Decimal? // mm + avgTemperature Float? // celsius + avgHumidity Decimal? // percentage + + // Agricultural phases + plantingStart DateTime? + plantingEnd DateTime? + growingStart DateTime? + growingEnd DateTime? + harvestStart DateTime? + harvestEnd DateTime? + + // Region and scope + region String? + province String? + country String @default("Indonesia") + + // Crop recommendations + recommendedCrops String[] // suitable crops for this season + notRecommendedCrops String[] // crops to avoid + + // Market expectations + expectedDemand DemandLevel? + priceOutlook PriceTrend? + marketNotes String? + + // Agricultural activities + activities Json? // structured data for farming activities + + // Status + isActive Boolean @default(true) + isCurrent Boolean @default(false) + + description String? + notes String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + plots PlotSeason[] + harvests SeasonHarvest[] + weatherData SeasonWeather[] + + @@unique([seasonType, year, region]) + @@index([seasonType, year]) + @@index([region]) + @@index([startDate, endDate]) + @@index([isActive]) + @@index([isCurrent]) + @@map("seasons") +} + +model PlotSeason { + id String @id @default(cuid()) + plotId String + seasonId String + productId String? + variantId String? + + // Planting details + plantedDate DateTime? + plantedArea Decimal? // hectares actually planted + seedVariety String? + + // Expected outcomes + expectedYield Decimal? // kg per hectare + expectedHarvest Decimal? // total kg expected + expectedHarvestDate DateTime? + + // Actual outcomes + actualYield Decimal? // kg per hectare + actualHarvest Decimal? // total kg harvested + actualHarvestDate DateTime? + + status PlantingStatus @default(PLANNED) + notes String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + plot Plot @relation(fields: [plotId], references: [id]) + season Season @relation(fields: [seasonId], references: [id]) + product Product? @relation(fields: [productId], references: [id]) + variant ProductVariant? @relation(fields: [variantId], references: [id]) + + @@unique([plotId, seasonId]) + @@index([plotId, seasonId]) + @@index([status]) + @@index([plantedDate]) + @@index([productId, variantId]) + @@map("plot_seasons") +} + +model SeasonHarvest { + id String @id @default(cuid()) + seasonId String + farmerId String + productId String + variantId String? + + totalQuantity Decimal + averageQuality QualityGrade + totalValue Decimal + + harvestCount Int @default(1) + firstHarvest DateTime + lastHarvest DateTime? + + notes String? + createdAt DateTime @default(now()) + + // Relations + season Season @relation(fields: [seasonId], references: [id]) + farmer Farmer @relation(fields: [farmerId], references: [id]) + product Product @relation(fields: [productId], references: [id]) + variant ProductVariant? @relation(fields: [variantId], references: [id]) + + @@index([seasonId, farmerId]) + @@index([productId, variantId]) + @@index([firstHarvest]) + @@index([averageQuality]) + @@map("season_harvests") +} + +model SeasonWeather { + id String @id @default(cuid()) + seasonId String + region String + + // Aggregated weather data + totalRainfall Decimal? // mm + avgTemperature Float? // celsius + minTemperature Float? // celsius + maxTemperature Float? // celsius + avgHumidity Decimal? // percentage + + // Extreme events + droughtDays Int? @default(0) + floodDays Int? @default(0) + stormCount Int? @default(0) + + // Impact assessment + cropDamage Decimal? // percentage + yieldImpact Decimal? // percentage change + + notes String? + createdAt DateTime @default(now()) + + // Relations + season Season @relation(fields: [seasonId], references: [id]) + + @@unique([seasonId, region]) + @@index([seasonId, region]) + @@index([totalRainfall]) + @@index([avgTemperature]) + @@map("season_weather") +} \ No newline at end of file diff --git a/schemas/training.prisma b/schemas/training.prisma new file mode 100644 index 0000000..6d958bb --- /dev/null +++ b/schemas/training.prisma @@ -0,0 +1,119 @@ +// Training & Certification Domain +// Contains training programs, certifications, and farmer development + +// Training-specific Enums +enum TrainingStatus { + ENROLLED + IN_PROGRESS + COMPLETED + DROPPED +} + +enum CertificationStatus { + PENDING + APPROVED + REJECTED + EXPIRED +} + +// Training Models +model Training { + id String @id @default(cuid()) + title String + description String? + content String? + category String + duration Int? // in minutes + level String? // beginner, intermediate, advanced + prerequisites String? + maxParticipants Int? // Maximum number of participants + createdBy String? // Admin who created the training + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmerTrainings FarmerTraining[] + + @@index([category]) + @@index([level]) + @@index([isActive]) + @@index([duration]) + @@index([createdBy]) + @@map("trainings") +} + +model FarmerTraining { + id String @id @default(cuid()) + farmerId String + trainingId String + status TrainingStatus @default(ENROLLED) + progress Int @default(0) // percentage + score Float? + completedAt DateTime? + assignedBy String? // Admin who assigned the farmer to training + assignedAt DateTime? // When the assignment was made + assignmentReason String? // Reason for assignment + deadline DateTime? // Expected completion deadline + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmer Farmer @relation(fields: [farmerId], references: [id], onDelete: Cascade) + training Training @relation(fields: [trainingId], references: [id], onDelete: Cascade) + + @@unique([farmerId, trainingId]) + @@index([farmerId]) + @@index([trainingId]) + @@index([status]) + @@index([completedAt]) + @@index([progress]) + @@index([assignedBy]) + @@index([assignedAt]) + @@index([deadline]) + @@map("farmer_trainings") +} + +model Certification { + id String @id @default(cuid()) + name String + description String? + validityPeriod Int? // in months + requirements String? + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmerCertifications FarmerCertification[] + + @@index([name]) + @@index([isActive]) + @@index([validityPeriod]) + @@map("certifications") +} + +model FarmerCertification { + id String @id @default(cuid()) + farmerId String + certificationId String + status CertificationStatus @default(PENDING) + issuedDate DateTime? + expiryDate DateTime? + certificateNumber String? + notes String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmer Farmer @relation(fields: [farmerId], references: [id], onDelete: Cascade) + certification Certification @relation(fields: [certificationId], references: [id], onDelete: Cascade) + + @@unique([farmerId, certificationId]) + @@index([farmerId]) + @@index([certificationId]) + @@index([status]) + @@index([expiryDate]) + @@index([issuedDate]) + @@map("farmer_certifications") +} \ No newline at end of file diff --git a/schemas/user.prisma b/schemas/user.prisma new file mode 100644 index 0000000..cd5a3ef --- /dev/null +++ b/schemas/user.prisma @@ -0,0 +1,261 @@ +// User Management Domain +// Contains User, Farmer, Buyer, Administrator models and related enums + +// User Status Enums +enum FarmerStatus { + ACTIVE + INACTIVE + SUSPENDED + PENDING_VERIFICATION + BLACKLISTED +} + +enum BuyerStatus { + ACTIVE + INACTIVE + SUSPENDED + PENDING_VERIFICATION + BLACKLISTED +} + +enum BuyerType { + INDIVIDUAL + WHOLESALER + RETAILER + PROCESSOR + EXPORTER + COOPERATIVE + GOVERNMENT + RESTAURANT + HOTEL +} + +enum BusinessSize { + MICRO + SMALL + MEDIUM + LARGE + ENTERPRISE +} + +enum LandOwnership { + OWNER + TENANT + SHARECROPPER + COOPERATIVE_MEMBER + GOVERNMENT_LEASE + FAMILY_LAND + OTHER +} + +// User Models +model User { + id String @id @default(cuid()) + email String @unique + phone String? + name String + role UserRole + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farmer Farmer? + buyer Buyer? + administrator Administrator? + notifications NotificationRecipient[] + + @@map("users") +} + +model Farmer { + id String @id @default(cuid()) + userId String @unique + farmerCode String @unique + name String + phone String? + email String? + dateOfBirth DateTime? + gender Gender? + maritalStatus MaritalStatus? + spouseName String? + numberOfChildren Int? @default(0) + religionId String? + religion Religion? @relation(fields: [religionId], references: [id]) + nationalityId String? @default("indonesia") + nationality Country? @relation("FarmerNationality", fields: [nationalityId], references: [id]) + address String? + village String? + district String? + province String? + postalCode String? + countryId String? @default("indonesia") + country Country? @relation("FarmerCountry", fields: [countryId], references: [id]) + + // Location data + latitude Float? + longitude Float? + addressGeoJson Json? // GeoJSON point for precise location + + // Identity information + identityType IdentityType? + identityNumber String? + identityExpiry DateTime? + + // Banking information + bankAccount String? + bankName String? + accountHolderName String? + + // Photos and attachments + profilePhotoUrl String? + idCardFrontUrl String? + idCardBackUrl String? + + // Verification status + isVerified Boolean @default(false) + verificationDate DateTime? + verifiedBy String? // admin ID who verified + verificationNotes String? + + // Farming experience + farmingExperience Int? // years of experience + educationLevelId String? + educationLevel EducationLevel? @relation(fields: [educationLevelId], references: [id]) + occupation String? // primary occupation if not full-time farmer + monthlyIncome Decimal? // estimated monthly income + landOwnership LandOwnership? + primaryCrop String? + farmingMethods String[] // organic, conventional, etc. + hasVehicle Boolean? // for transportation + vehicleType String? // motorcycle, car, truck, etc. + hasSmartphone Boolean? @default(true) + internetAccess Boolean? @default(true) + + // Emergency contact + emergencyContactName String? + emergencyContactPhone String? + emergencyContactRelation String? + + // Status and metadata + status FarmerStatus @default(ACTIVE) + notes String? + joinedAt DateTime @default(now()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + farms Farm[] + trainings FarmerTraining[] + certifications FarmerCertification[] + procurements Procurement[] + harvests Harvest[] + attachments FarmerAttachment[] + workers FarmWorker[] + laborRecords LaborRecord[] + laborSchedules LaborSchedule[] + equipment Equipment[] + assets Asset[] + financialRecords FinancialRecord[] + reviews BuyerReview[] + contracts Contract[] + seasonHarvests SeasonHarvest[] + + @@index([farmerCode]) + @@index([userId]) + @@index([status]) + @@index([joinedAt]) + @@index([latitude, longitude]) + @@map("farmers") +} + +model Buyer { + id String @id @default(cuid()) + userId String @unique + buyerCode String @unique + name String + company String? + phone String? + email String? + address String? + + // Enhanced buyer details + buyerType BuyerType? + businessLicense String? + taxNumber String? + contactPerson String? + contactPersonPhone String? + paymentTerms Int? // days + creditLimit Decimal? + preferredProducts String[] + qualityRequirements String? + + // Location and logistics + warehouseAddress String? + deliveryPreference String? + operatingRadius Decimal? // km + + // Business details + businessSize BusinessSize? + yearEstablished Int? + annualVolume Decimal? // estimated annual purchase volume + employeeCount Int? + website String? + + // Financial information + bankAccount String? + bankName String? + accountHolderName String? + + // Verification and status + isVerified Boolean @default(false) + verificationDate DateTime? + verifiedBy String? // admin ID who verified + verificationNotes String? + status BuyerStatus @default(ACTIVE) + + // Photos and documents + profilePhotoUrl String? + businessLicenseUrl String? + taxCertificateUrl String? + + // Preferences + preferredPickupDays String[] // Mon, Tue, Wed, etc. + preferredPickupTime String? + minimumOrderQuantity Decimal? + maximumOrderQuantity Decimal? + + notes String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + procurements Procurement[] + reviews BuyerReview[] + contracts Contract[] + + @@index([buyerCode]) + @@index([userId]) + @@index([status]) + @@index([buyerType]) + @@map("buyers") +} + +model Administrator { + id String @id @default(cuid()) + userId String @unique + adminCode String @unique + name String + phone String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([adminCode]) + @@index([userId]) + @@map("administrators") +} \ No newline at end of file diff --git a/schemas/weather.prisma b/schemas/weather.prisma new file mode 100644 index 0000000..0aebc13 --- /dev/null +++ b/schemas/weather.prisma @@ -0,0 +1,66 @@ +// Weather & Climate Domain +// Contains weather data, forecasts, and climate tracking + +model WeatherData { + id String @id @default(cuid()) + farmId String? + plotId String? + latitude Float + longitude Float + date DateTime + temperature Float? // celsius + humidity Float? // percentage + rainfall Float? // mm + windSpeed Float? // km/h + windDirection String? // N, NE, E, SE, S, SW, W, NW + pressure Float? // hPa + uvIndex Float? + visibility Float? // km + weatherCondition String? // sunny, cloudy, rainy, etc. + dataSource String? // manual, weather_station, api + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + farm Farm? @relation(fields: [farmId], references: [id]) + plot Plot? @relation(fields: [plotId], references: [id]) + + @@index([farmId, plotId]) + @@index([latitude, longitude]) + @@index([date]) + @@index([weatherCondition]) + @@index([dataSource]) + @@index([temperature]) + @@index([rainfall]) + @@map("weather_data") +} + +model WeatherForecast { + id String @id @default(cuid()) + farmId String? + plotId String? + latitude Float + longitude Float + forecastDate DateTime + minTemperature Float? + maxTemperature Float? + humidity Float? + rainfall Float? + windSpeed Float? + weatherCondition String? + confidence Float? // percentage + source String + createdAt DateTime @default(now()) + + // Relations + farm Farm? @relation(fields: [farmId], references: [id]) + plot Plot? @relation(fields: [plotId], references: [id]) + + @@index([farmId, plotId]) + @@index([latitude, longitude]) + @@index([forecastDate]) + @@index([weatherCondition]) + @@index([source]) + @@index([confidence]) + @@map("weather_forecasts") +} \ No newline at end of file diff --git a/training-user-stories.md b/training-user-stories.md new file mode 100644 index 0000000..75e25a9 --- /dev/null +++ b/training-user-stories.md @@ -0,0 +1,518 @@ +# Training System User Stories + +## 📚 **Epic: Farmer Training & Development Platform** + +### **Theme: Skill Development & Certification** + +--- + +## **👨‍🌾 As a Farmer:** + +### **Training Participation** + +#### **User Story 1: View Assigned Training Programs** +```gherkin +Feature: View and participate in assigned training programs + +Scenario: View assigned training programs + Given I am logged into the farmer dashboard + When I navigate to the training section + Then I should see training programs assigned to me by admin: + - Training title and description + - Duration and schedule + - Level and prerequisites + - Assignment date and deadline + And I should see training status (ENROLLED, IN_PROGRESS, COMPLETED) + And I can view detailed training information +``` + +#### **User Story 2: Receive Training Assignment Notification** +```gherkin +Scenario: Receive notification when assigned to training + Given an admin has assigned me to a training program + When the assignment is created + Then I should receive a notification stating: + - Training program name + - Assignment reason + - Training schedule and duration + - Expected completion date + And the training should appear in my "My Trainings" dashboard + And my status should be set to "ENROLLED" +``` + +### **Training Progress & Learning** + +#### **User Story 3: Access Training Content** +```gherkin +Feature: Complete training and track progress + +Scenario: Access training content + Given I am enrolled in "Integrated Pest Management" training + When I click "Start Learning" + Then I should see the training content modules: + - Video lessons + - Reading materials + - Interactive quizzes + - Practical exercises + And my progress should be tracked as I complete sections + And I can bookmark important content for later reference +``` + +#### **User Story 4: Complete Training Modules** +```gherkin +Scenario: Complete training modules progressively + Given I am in progress with a training program + When I complete each module + Then my progress percentage should update automatically + And I should see my completion status on the dashboard + And next modules should unlock sequentially + When I complete all modules with 80% average score + Then my status should change to "COMPLETED" + And I should be eligible for certification + And I should receive a completion certificate +``` + +#### **User Story 5: Track Learning Progress** +```gherkin +Scenario: Monitor my learning journey + Given I have multiple trainings in progress + When I view my training dashboard + Then I should see: + - Overall progress percentage for each training + - Time spent learning this week + - Upcoming deadlines or recommendations + - My skill level improvements + - Badges or achievements earned +``` + +### **Certification Management** + +#### **User Story 6: Apply for Certification** +```gherkin +Feature: Earn and manage certifications + +Scenario: Apply for certification after training + Given I have completed "Organic Farming Basics" training + And my final score is above 85% + When I click "Apply for Certification" + Then my certification status should be "PENDING" + And an admin should be notified for review + And I should see estimated review time (3-5 business days) + And I can upload additional supporting documents if required +``` + +#### **User Story 7: View My Certifications** +```gherkin +Scenario: Manage my certification portfolio + Given I have earned certifications + When I visit my profile certification section + Then I should see all my certifications with: + - Certificate name and issuing body + - Issue date and expiry date + - Digital certificate download link (PDF) + - Verification QR code + - Renewal requirements and timeline + And I should get notifications 30 days before expiry + And I can share certifications with buyers or cooperatives +``` + +--- + +## **🏢 As a Buyer:** + +### **Farmer Qualification Verification** + +#### **User Story 8: Verify Farmer Qualifications** +```gherkin +Feature: Verify farmer qualifications during procurement + +Scenario: View farmer certifications during procurement + Given I am evaluating a farmer for procurement + When I view their profile + Then I should see their completed trainings and certifications: + - Organic farming certifications + - Pest management training completion + - Quality standards training + - Safety and compliance certifications + And I can verify certificate authenticity via QR codes + And I can see their training scores and completion dates +``` + +#### **User Story 9: Filter Farmers by Certifications** +```gherkin +Scenario: Find qualified farmers for specific requirements + Given I need organic certified produce for export + When I search for farmers in the procurement section + Then I can filter by: + - Certification type (Organic, GAP, Halal, etc.) + - Certification validity status + - Training completion in specific areas + - Skill level and experience + And see only farmers meeting my quality requirements + And view their certification verification status +``` + +--- + +## **👨‍💼 As an Administrator:** + +### **Authentication & Access** + +#### **User Story 10: Admin Login** +```gherkin +Feature: Admin authentication and access control + +Scenario: Admin login to training management system + Given I am an authorized administrator + When I navigate to the admin login page + And I enter my valid credentials + Then I should be logged into the admin dashboard + And I should see training management options + And I should have access to all training-related features +``` + +### **Training Content Management** + +#### **User Story 11: Create Training Programs** +```gherkin +Feature: Create and manage training programs + +Scenario: Create comprehensive training program + Given I am logged into the admin training management dashboard + When I create a new training program + Then I should specify: + - Title: "Sustainable Pepper Cultivation" + - Description and learning objectives + - Category: Crop-specific training + - Skill level: Beginner/Intermediate/Advanced + - Duration: 180 minutes + - Prerequisites: Basic farming knowledge + - Training modules and content structure + - Assessment criteria and passing score + - Certification eligibility requirements + And I can upload multimedia content (videos, PDFs, images) + And set the training as active/inactive +``` + +#### **User Story 12: Edit Training Programs** +```gherkin +Scenario: Edit existing training program + Given I am viewing a training program "Organic Farming Basics" + When I click "Edit Training" + Then I should be able to modify: + - Title and description + - Category and skill level + - Duration and prerequisites + - Training content and modules + - Assessment criteria + - Active/inactive status + And save the changes + And notify assigned farmers of updates +``` + +#### **User Story 13: Delete Training Programs** +```gherkin +Scenario: Delete training program + Given I am viewing a training program that needs to be removed + When I click "Delete Training" + Then I should see a confirmation dialog + And I should be warned if farmers are currently enrolled + When I confirm deletion + Then the training should be removed from the system + And enrolled farmers should be notified of cancellation +``` + +#### **User Story 14: View All Training Programs** +```gherkin +Scenario: View comprehensive training program list + Given I am in the admin training management dashboard + When I navigate to the training programs section + Then I should see all training programs with: + - Title and category + - Status (active/inactive) + - Number of enrolled farmers + - Creation and last modified dates + - Completion statistics + And I can filter by category, status, or date + And I can sort by various criteria +``` + +#### **User Story 15: View Individual Training Program** +```gherkin +Scenario: View detailed training program information + Given I am viewing the training programs list + When I click on a specific training program + Then I should see detailed information: + - Complete training description and objectives + - All modules and content structure + - Assessment criteria and requirements + - List of enrolled farmers with their progress + - Completion statistics and analytics + - Farmer feedback and ratings + And I can edit or delete the training from this view +``` + +### **Farmer Assignment Management** + +#### **User Story 16: Select Farmers for Training** +```gherkin +Feature: Assign farmers to training programs + +Scenario: Assign farmers to training program + Given I am viewing a training program "Integrated Pest Management" + When I click "Assign Farmers" + Then I should see a list of available farmers with: + - Farmer name and location + - Crop types and farm details + - Previous training history + - Skill level and certifications + And I can filter farmers by: + - Location/region + - Crop type + - Skill level + - Previous training completion + And I can select multiple farmers + And I can add assignment notes/reasons + When I confirm assignment + Then selected farmers should be enrolled in the training + And farmers should receive assignment notifications +``` + +#### **User Story 17: Monitor Training Effectiveness** +```gherkin +Scenario: Analyze training program performance + Given training programs have been running for 3 months + When I view training analytics dashboard + Then I should see comprehensive metrics: + - Assignment numbers per program and category + - Completion rates by farmer demographics + - Average scores and assessment results + - Time spent per module + - Farmer feedback and ratings + - Drop-off points in training content + - Geographic distribution of participants + - ROI impact on farm productivity + And I can export reports for stakeholders +``` + +### **Certification Review & Approval** + +#### **User Story 18: Process Certification Applications** +```gherkin +Feature: Review and approve certifications efficiently + +Scenario: Review certification applications systematically + Given farmers have applied for certifications + When I access the certification review queue + Then I should see pending applications with: + - Farmer profile and farm details + - Training completion records and scores + - Assessment results and practical evaluations + - Supporting documents and evidence + - Previous certification history + And I can sort by application date, urgency, or type + And I can assign applications to specialist reviewers +``` + +#### **User Story 19: Approve and Issue Certifications** +```gherkin +Scenario: Complete certification approval process + Given I am reviewing a certification application for "Organic Farming" + And the farmer has met all requirements: + - Training completion with 85% score + - Practical assessment passed + - Supporting documentation verified + When I approve the application + Then the farmer's status should change to "APPROVED" + And a digital certificate should be auto-generated with: + - Unique certificate number + - QR code for verification + - Expiry date (24 months for organic certification) + And the farmer should receive approval notification + And the certificate should be available for download + And the farmer's profile should reflect the new certification +``` + +--- + +## **📊 Advanced User Stories:** + +### **Personalized Learning Paths** + +#### **User Story 20: AI-Driven Training Recommendations** +```gherkin +Feature: Intelligent training recommendations + +As a Farmer +I want to receive personalized training recommendations +So that I can improve my specific farming challenges + +Scenario: Receive targeted recommendations based on farm data + Given my farm profile shows: + - Crop: Pepper cultivation + - Recent issue: Pest damage reported + - Skill level: Intermediate + - Location: West Java + When I view my dashboard + Then I should see recommended trainings: + - "Integrated Pest Management for Pepper" (High Priority) + - "Organic Pest Control Methods" (Medium Priority) + - "Beneficial Insects in Agriculture" (Low Priority) + And recommendations should explain why they're relevant + And I can see success stories from similar farmers +``` + +### **Group Training & Community Learning** + +#### **User Story 21: Collaborative Learning Sessions** +```gherkin +Feature: Group training and peer learning + +As a Farmer +I want to join group training sessions with other farmers +So that I can learn collaboratively and share experiences + +Scenario: Participate in regional group training + Given there's a group training "Sustainable Rice Farming" in my district + And 15 other farmers from nearby villages are enrolled + When I join the group session + Then I should be able to: + - Participate in live discussions + - Share my farming experiences + - Ask questions to trainers and peers + - Access group chat and resources + - Schedule follow-up practice sessions + And I can connect with other farmers for ongoing support +``` + +### **Mobile Learning & Offline Access** + +#### **User Story 22: Mobile-Optimized Learning** +```gherkin +Feature: Mobile learning with offline capabilities + +As a Farmer +I want to access training on my smartphone during field work +So that I can learn flexibly without internet dependency + +Scenario: Offline training access + Given I have limited internet connectivity in my farm area + When I download training content while connected + Then I should be able to: + - Access all downloaded materials offline + - Complete quizzes and assessments + - Take notes and bookmark content + - Record my progress locally + And when I reconnect to internet: + - All progress should sync automatically + - New content should be available for download + - Notifications should be received +``` + +### **Training Impact & ROI Tracking** + +#### **User Story 23: Measure Training Effectiveness** +```gherkin +Feature: Training impact analytics + +As an Administrator +I want to track training impact on farm performance +So that I can improve training programs and demonstrate ROI + +Scenario: Analyze training return on investment + Given farmers have completed productivity training over 6 months + When I generate impact analytics reports + Then I should see correlations between: + - Training completion and yield improvements (% increase) + - Certification status and product quality grades + - Skill level progression and income increases + - Training investment vs. productivity gains + - Farmer satisfaction vs. knowledge retention + - Regional performance vs. training participation rates + And I can identify most effective training content + And create evidence-based improvements +``` + +--- + +## **🎯 Success Metrics & KPIs:** + +### **Engagement Metrics** +- **Enrollment Rate**: 75% of active farmers enroll in at least one training annually +- **Completion Rate**: 80% of enrolled farmers complete their training programs +- **Retention Rate**: 90% of farmers continue learning after first training +- **Mobile Usage**: 60% of training consumption happens on mobile devices + +### **Learning Effectiveness** +- **Knowledge Retention**: 85% average score on post-training assessments +- **Practical Application**: 70% of farmers implement learned techniques +- **Certification Rate**: 60% of completed trainings lead to certifications +- **Skill Progression**: 50% improvement in skill assessments + +### **Business Impact** +- **Productivity Improvement**: 25% average yield increase post-training +- **Quality Enhancement**: 30% improvement in product quality grades +- **Income Growth**: 20% increase in farmer income within 12 months +- **Certification Value**: 15% premium for certified produce + +### **User Satisfaction** +- **Training Quality Rating**: 4.5/5 average rating +- **Content Relevance**: 85% find training directly applicable +- **Platform Usability**: 90% satisfaction with mobile app experience +- **Support Quality**: 95% satisfaction with help and guidance + +--- + +## **📱 Technical Implementation Considerations:** + +### **Platform Requirements** +- **Mobile-First Design**: Responsive design optimized for smartphones +- **Offline Capability**: Progressive Web App (PWA) with offline content +- **Multi-language Support**: Bahasa Indonesia, Javanese, and English +- **Low Bandwidth**: Optimized for 2G/3G networks in rural areas + +### **Content Management** +- **Rich Media Support**: Videos, interactive presentations, PDF documents +- **Progressive Content**: Modular structure with prerequisite management +- **Assessment Engine**: Quizzes, practical evaluations, peer assessments +- **Analytics Integration**: Detailed learning analytics and progress tracking + +### **Integration Features** +- **Farm Data Integration**: Connect training to actual farm performance +- **Certification Verification**: QR codes and blockchain verification +- **Payment Integration**: Premium training content and certification fees +- **Communication Tools**: Chat, forums, and mentorship connections + +### **Security & Compliance** +- **Data Privacy**: GDPR compliance for farmer personal data +- **Certificate Security**: Tamper-proof digital certificates +- **Content Protection**: DRM for premium training content +- **Access Control**: Role-based permissions for different user types + +--- + +## **🚀 Implementation Roadmap:** + +### **Phase 1: Core Training Platform (Months 1-2)** +- Basic training content management +- Farmer enrollment and progress tracking +- Mobile-responsive interface +- Basic certification workflow + +### **Phase 2: Enhanced Learning Experience (Months 3-4)** +- Offline content access +- Interactive assessments and quizzes +- Group training capabilities +- Advanced progress analytics + +### **Phase 3: Intelligent Features (Months 5-6)** +- AI-driven personalized recommendations +- Training impact tracking and ROI analysis +- Integration with farm management data +- Advanced certification verification + +### **Phase 4: Community & Ecosystem (Months 7-8)** +- Peer learning and mentorship +- Marketplace integration for certified farmers +- Third-party training provider integration +- Advanced analytics and reporting \ No newline at end of file