diff --git a/package.json b/package.json index e756e96..91dd8a2 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,9 @@ "seed:demographics": "bun prisma/seed.ts demographics", "seed:divisions": "bun prisma/seed.ts divisions", "seed:services": "bun prisma/seed.ts services", - "seed:dashboard": "bun prisma/seed.ts dashboard" + "seed:documents": "bun prisma/seed.ts documents", + "seed:dashboard": "bun prisma/seed.ts dashboard", + "seed:phase2": "bun prisma/seed.ts phase2" }, "dependencies": { "@better-auth/cli": "^1.4.18", diff --git a/prisma/migrations/20260327041936_add_umkm_fields/migration.sql b/prisma/migrations/20260327041936_add_umkm_fields/migration.sql new file mode 100644 index 0000000..ebbffd1 --- /dev/null +++ b/prisma/migrations/20260327041936_add_umkm_fields/migration.sql @@ -0,0 +1,15 @@ +/* + Warnings: + + - Added the required column `name` to the `Umkm` table without a default value. This is not possible if the table is not empty. + - Added the required column `owner` to the `Umkm` table without a default value. This is not possible if the table is not empty. + - Added the required column `updatedAt` to the `Umkm` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Umkm" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "description" TEXT, +ADD COLUMN "name" TEXT NOT NULL, +ADD COLUMN "owner" TEXT NOT NULL, +ADD COLUMN "productType" TEXT, +ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL; diff --git a/prisma/migrations/20260327042029_add_posyandu_security_fields/migration.sql b/prisma/migrations/20260327042029_add_posyandu_security_fields/migration.sql new file mode 100644 index 0000000..1b245c1 --- /dev/null +++ b/prisma/migrations/20260327042029_add_posyandu_security_fields/migration.sql @@ -0,0 +1,36 @@ +/* + Warnings: + + - A unique constraint covering the columns `[reportNumber]` on the table `SecurityReport` will be added. If there are existing duplicate values, this will fail. + - Added the required column `location` to the `Posyandu` table without a default value. This is not possible if the table is not empty. + - Added the required column `name` to the `Posyandu` table without a default value. This is not possible if the table is not empty. + - Added the required column `schedule` to the `Posyandu` table without a default value. This is not possible if the table is not empty. + - Added the required column `type` to the `Posyandu` table without a default value. This is not possible if the table is not empty. + - Added the required column `updatedAt` to the `Posyandu` table without a default value. This is not possible if the table is not empty. + - Added the required column `description` to the `SecurityReport` table without a default value. This is not possible if the table is not empty. + - Added the required column `reportNumber` to the `SecurityReport` table without a default value. This is not possible if the table is not empty. + - Added the required column `reportedBy` to the `SecurityReport` table without a default value. This is not possible if the table is not empty. + - Added the required column `title` to the `SecurityReport` table without a default value. This is not possible if the table is not empty. + - Added the required column `updatedAt` to the `SecurityReport` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Posyandu" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "location" TEXT NOT NULL, +ADD COLUMN "name" TEXT NOT NULL, +ADD COLUMN "schedule" TEXT NOT NULL, +ADD COLUMN "type" TEXT NOT NULL, +ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL; + +-- AlterTable +ALTER TABLE "SecurityReport" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "description" TEXT NOT NULL, +ADD COLUMN "location" TEXT, +ADD COLUMN "reportNumber" TEXT NOT NULL, +ADD COLUMN "reportedBy" TEXT NOT NULL, +ADD COLUMN "status" TEXT NOT NULL DEFAULT 'BARU', +ADD COLUMN "title" TEXT NOT NULL, +ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL; + +-- CreateIndex +CREATE UNIQUE INDEX "SecurityReport_reportNumber_key" ON "SecurityReport"("reportNumber"); diff --git a/prisma/migrations/20260327060438_add_phase2_fields/migration.sql b/prisma/migrations/20260327060438_add_phase2_fields/migration.sql new file mode 100644 index 0000000..59f65d3 --- /dev/null +++ b/prisma/migrations/20260327060438_add_phase2_fields/migration.sql @@ -0,0 +1,49 @@ +/* + Warnings: + + - A unique constraint covering the columns `[transactionNumber]` on the table `BudgetTransaction` will be added. If there are existing duplicate values, this will fail. + - Added the required column `amount` to the `BudgetTransaction` table without a default value. This is not possible if the table is not empty. + - Added the required column `category` to the `BudgetTransaction` table without a default value. This is not possible if the table is not empty. + - Added the required column `date` to the `BudgetTransaction` table without a default value. This is not possible if the table is not empty. + - Added the required column `transactionNumber` to the `BudgetTransaction` table without a default value. This is not possible if the table is not empty. + - Added the required column `type` to the `BudgetTransaction` table without a default value. This is not possible if the table is not empty. + - Added the required column `companyName` to the `EmploymentRecord` table without a default value. This is not possible if the table is not empty. + - Added the required column `position` to the `EmploymentRecord` table without a default value. This is not possible if the table is not empty. + - Added the required column `startDate` to the `EmploymentRecord` table without a default value. This is not possible if the table is not empty. + - Added the required column `type` to the `HealthRecord` table without a default value. This is not possible if the table is not empty. + - Added the required column `eventDate` to the `PopulationDynamic` table without a default value. This is not possible if the table is not empty. + - Added the required column `residentName` to the `PopulationDynamic` table without a default value. This is not possible if the table is not empty. + - Added the required column `type` to the `PopulationDynamic` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "BudgetTransaction" ADD COLUMN "amount" DOUBLE PRECISION NOT NULL, +ADD COLUMN "category" TEXT NOT NULL, +ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "date" TIMESTAMP(3) NOT NULL, +ADD COLUMN "description" TEXT, +ADD COLUMN "transactionNumber" TEXT NOT NULL, +ADD COLUMN "type" TEXT NOT NULL; + +-- AlterTable +ALTER TABLE "EmploymentRecord" ADD COLUMN "companyName" TEXT NOT NULL, +ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "endDate" TIMESTAMP(3), +ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true, +ADD COLUMN "position" TEXT NOT NULL, +ADD COLUMN "startDate" TIMESTAMP(3) NOT NULL; + +-- AlterTable +ALTER TABLE "HealthRecord" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "notes" TEXT, +ADD COLUMN "type" TEXT NOT NULL; + +-- AlterTable +ALTER TABLE "PopulationDynamic" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "description" TEXT, +ADD COLUMN "eventDate" TIMESTAMP(3) NOT NULL, +ADD COLUMN "residentName" TEXT NOT NULL, +ADD COLUMN "type" TEXT NOT NULL; + +-- CreateIndex +CREATE UNIQUE INDEX "BudgetTransaction_transactionNumber_key" ON "BudgetTransaction"("transactionNumber"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 829c764..b03d65c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -357,47 +357,88 @@ model SatisfactionRating { // --- STUBS FOR PHASE 2+ (To maintain relations) --- model HealthRecord { - id String @id @default(cuid()) + id String @id @default(cuid()) residentId String resident Resident @relation(fields: [residentId], references: [id]) recordedBy String - recorder User @relation(fields: [recordedBy], references: [id]) + recorder User @relation(fields: [recordedBy], references: [id]) + type String // "Pemeriksaan", "Imunisasi", "Ibu Hamil" + notes String? + createdAt DateTime @default(now()) } model EmploymentRecord { - id String @id @default(cuid()) - residentId String - resident Resident @relation(fields: [residentId], references: [id]) + id String @id @default(cuid()) + residentId String + resident Resident @relation(fields: [residentId], references: [id]) + companyName String + position String + startDate DateTime + endDate DateTime? + isActive Boolean @default(true) + createdAt DateTime @default(now()) } model PopulationDynamic { - id String @id @default(cuid()) + id String @id @default(cuid()) documentedBy String - documentor User @relation(fields: [documentedBy], references: [id]) + documentor User @relation(fields: [documentedBy], references: [id]) + type String // "KELAHIRAN", "KEMATIAN", "KEDATANGAN", "KEPERGIAN" + residentName String + eventDate DateTime + description String? + createdAt DateTime @default(now()) } model BudgetTransaction { - id String @id @default(cuid()) - createdBy String - creator User @relation(fields: [createdBy], references: [id]) + id String @id @default(cuid()) + createdBy String + creator User @relation(fields: [createdBy], references: [id]) + transactionNumber String @unique + type String // "PENGELUARAN", "PENDAPATAN" + category String + amount Float + description String? + date DateTime + createdAt DateTime @default(now()) } model Umkm { - id String @id @default(cuid()) - banjarId String? - banjar Banjar? @relation(fields: [banjarId], references: [id]) + id String @id @default(cuid()) + banjarId String? + banjar Banjar? @relation(fields: [banjarId], references: [id]) + name String + owner String + productType String? + description String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model Posyandu { - id String @id @default(cuid()) + id String @id @default(cuid()) coordinatorId String? - coordinator User? @relation(fields: [coordinatorId], references: [id]) + coordinator User? @relation(fields: [coordinatorId], references: [id]) + name String + location String + schedule String + type String // "Ibu dan Anak", "Lansia", etc. + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model SecurityReport { - id String @id @default(cuid()) - assignedTo String? - assignee User? @relation(fields: [assignedTo], references: [id]) + id String @id @default(cuid()) + assignedTo String? + assignee User? @relation(fields: [assignedTo], references: [id]) + reportNumber String @unique + title String + description String + location String? + reportedBy String + status String @default("BARU") // BARU, DIPROSES, SELESAI + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } // --- ENUMS --- diff --git a/prisma/seed.ts b/prisma/seed.ts index b807288..d74aaad 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -2,7 +2,7 @@ import "dotenv/config"; import { PrismaClient } from "../generated/prisma"; // Import all seeders -import { seedAdminUser, seedDemoUsers } from "./seeders/seed-auth"; +import { seedAdminUser, seedDemoUsers, seedApiKeys } from "./seeders/seed-auth"; import { seedBanjars, seedResidents, getBanjarIds } from "./seeders/seed-demographics"; import { seedDivisions, seedActivities, getDivisionIds } from "./seeders/seed-division-performance"; import { @@ -10,8 +10,12 @@ import { seedServiceLetters, seedEvents, seedInnovationIdeas, + seedComplaintUpdates, + getComplaintIds, } from "./seeders/seed-public-services"; +import { seedDocuments, seedDiscussions, seedDivisionMetrics } from "./seeders/seed-discussions"; import { seedDashboardMetrics } from "./seeders/seed-dashboard-metrics"; +import { seedPhase2 } from "./seeders/seed-phase2"; const prisma = new PrismaClient(); @@ -23,38 +27,53 @@ export async function runSeed() { console.log("🌱 Starting seed...\n"); // 1. Seed Authentication (Admin & Demo Users) - console.log("📁 [1/6] Authentication & Users"); + console.log("📁 [1/7] Authentication & Users"); const adminId = await seedAdminUser(); await seedDemoUsers(); + await seedApiKeys(adminId); console.log(); // 2. Seed Demographics (Banjars & Residents) - console.log("📁 [2/6] Demographics & Population"); + console.log("📁 [2/7] Demographics & Population"); await seedBanjars(); const banjarIds = await getBanjarIds(); await seedResidents(banjarIds); console.log(); // 3. Seed Division Performance (Divisions & Activities) - console.log("📁 [3/6] Division Performance"); + console.log("📁 [3/7] Division Performance"); const divisions = await seedDivisions(); const divisionIds = divisions.map((d) => d.id); await seedActivities(divisionIds); + await seedDivisionMetrics(divisionIds); console.log(); // 4. Seed Public Services (Complaints, Service Letters, Events, Innovation) - console.log("📁 [4/6] Public Services"); + console.log("📁 [4/7] Public Services"); await seedComplaints(adminId); await seedServiceLetters(adminId); await seedEvents(adminId); await seedInnovationIdeas(adminId); + const complaintIds = await getComplaintIds(); + await seedComplaintUpdates(complaintIds, adminId); console.log(); - // 5. Seed Dashboard Metrics (Budget, SDGs, Satisfaction) - console.log("📁 [5/6] Dashboard Metrics"); + // 5. Seed Documents & Discussions + console.log("📁 [5/7] Documents & Discussions"); + await seedDocuments(divisionIds, adminId); + await seedDiscussions(divisionIds, adminId); + console.log(); + + // 6. Seed Dashboard Metrics (Budget, SDGs, Satisfaction) + console.log("📁 [6/7] Dashboard Metrics"); await seedDashboardMetrics(); console.log(); + // 7. Seed Phase 2+ Features (UMKM, Posyandu, Security, etc.) + console.log("📁 [7/7] Phase 2+ Features"); + await seedPhase2(banjarIds, adminId); + console.log(); + console.log("✅ Seed finished successfully!\n"); } @@ -69,8 +88,9 @@ export async function runSpecificSeeder(name: string) { case "auth": case "users": console.log("📁 Authentication & Users"); - await seedAdminUser(); + const adminId = await seedAdminUser(); await seedDemoUsers(); + await seedApiKeys(adminId); break; case "demographics": @@ -87,17 +107,30 @@ export async function runSpecificSeeder(name: string) { const divisions = await seedDivisions(); const divisionIds = divisions.map((d) => d.id); await seedActivities(divisionIds); + await seedDivisionMetrics(divisionIds); break; case "complaints": case "services": case "public": console.log("📁 Public Services"); - const adminId = await seedAdminUser(); - await seedComplaints(adminId); - await seedServiceLetters(adminId); - await seedEvents(adminId); - await seedInnovationIdeas(adminId); + const pubAdminId = await seedAdminUser(); + await seedComplaints(pubAdminId); + await seedServiceLetters(pubAdminId); + await seedEvents(pubAdminId); + await seedInnovationIdeas(pubAdminId); + const compIds = await getComplaintIds(); + await seedComplaintUpdates(compIds, pubAdminId); + break; + + case "documents": + case "discussions": + console.log("📁 Documents & Discussions"); + const docAdminId = await seedAdminUser(); + const divs = await seedDivisions(); + const divIds = divs.map((d) => d.id); + await seedDocuments(divIds, docAdminId); + await seedDiscussions(divIds, docAdminId); break; case "dashboard": @@ -106,9 +139,18 @@ export async function runSpecificSeeder(name: string) { await seedDashboardMetrics(); break; + case "phase2": + case "features": + console.log("📁 Phase 2+ Features"); + const p2AdminId = await seedAdminUser(); + await seedBanjars(); + const p2BanjarIds = await getBanjarIds(); + await seedPhase2(p2BanjarIds, p2AdminId); + break; + default: console.error(`❌ Unknown seeder: ${name}`); - console.log("Available seeders: auth, demographics, divisions, complaints, dashboard"); + console.log("Available seeders: auth, demographics, divisions, complaints, documents, dashboard, phase2"); process.exit(1); } diff --git a/prisma/seeders/seed-auth.ts b/prisma/seeders/seed-auth.ts index 0a907d1..ba270d0 100644 --- a/prisma/seeders/seed-auth.ts +++ b/prisma/seeders/seed-auth.ts @@ -117,3 +117,45 @@ export async function seedDemoUsers() { console.log(`✅ Demo user created: ${demo.email}`); } } + +/** + * Seed API Keys + * Creates sample API keys for testing API access + */ +export async function seedApiKeys(adminId: string) { + console.log("Seeding API Keys..."); + + const existingKeys = await prisma.apiKey.findMany({ + where: { userId: adminId }, + }); + + if (existingKeys.length > 0) { + console.log("⏭️ API keys already exist, skipping"); + return; + } + + const apiKeys = [ + { + name: "Development Key", + key: "dev_key_" + generateId(), + userId: adminId, + isActive: true, + expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // 1 year + }, + { + name: "Production Key", + key: "prod_key_" + generateId(), + userId: adminId, + isActive: true, + expiresAt: null, + }, + ]; + + for (const apiKey of apiKeys) { + await prisma.apiKey.create({ + data: apiKey, + }); + } + + console.log("✅ API Keys seeded successfully"); +} diff --git a/prisma/seeders/seed-discussions.ts b/prisma/seeders/seed-discussions.ts new file mode 100644 index 0000000..bb9fab7 --- /dev/null +++ b/prisma/seeders/seed-discussions.ts @@ -0,0 +1,203 @@ +import { + DocumentCategory, + Priority, + PrismaClient, +} from "../../generated/prisma"; + +const prisma = new PrismaClient(); + +/** + * Seed Documents + * Creates sample documents for divisions (SK, laporan, dokumentasi) + */ +export async function seedDocuments(divisionIds: string[], userId: string) { + console.log("Seeding Documents..."); + + const documents = [ + { + title: "SK Kepala Desa No. 1/2025", + category: DocumentCategory.SURAT_KEPUTUSAN, + type: "PDF", + fileUrl: "/documents/sk-kepala-desa-001.pdf", + fileSize: 245000, + divisionId: divisionIds[0] || null, + uploadedBy: userId, + }, + { + title: "Laporan Keuangan Q1 2025", + category: DocumentCategory.LAPORAN_KEUANGAN, + type: "PDF", + fileUrl: "/documents/laporan-keuangan-q1-2025.pdf", + fileSize: 512000, + divisionId: divisionIds[0] || null, + uploadedBy: userId, + }, + { + title: "Dokumentasi Gotong Royong", + category: DocumentCategory.DOKUMENTASI, + type: "Gambar", + fileUrl: "/images/gotong-royong-2025.jpg", + fileSize: 1024000, + divisionId: divisionIds[3] || null, + uploadedBy: userId, + }, + { + title: "Notulensi Rapat Desa", + category: DocumentCategory.NOTULENSI_RAPAT, + type: "Dokumen", + fileUrl: "/documents/notulensi-rapat-desa.pdf", + fileSize: 128000, + divisionId: divisionIds[0] || null, + uploadedBy: userId, + }, + { + title: "Data Penduduk 2025", + category: DocumentCategory.UMUM, + type: "Excel", + fileUrl: "/documents/data-penduduk-2025.xlsx", + fileSize: 350000, + divisionId: null, + uploadedBy: userId, + }, + ]; + + for (const doc of documents) { + await prisma.document.create({ + data: doc, + }); + } + + console.log("✅ Documents seeded successfully"); +} + +/** + * Seed Discussions + * Creates sample discussions for divisions and activities + */ +export async function seedDiscussions(divisionIds: string[], userId: string) { + console.log("Seeding Discussions..."); + + const discussions = [ + { + message: "Mohon update progress pembangunan jalan", + senderId: userId, + divisionId: divisionIds[1] || null, + isResolved: false, + }, + { + message: "Baik, akan segera kami tindak lanjuti", + senderId: userId, + divisionId: divisionIds[1] || null, + isResolved: false, + parentId: null, // Will be set as reply + }, + { + message: "Jadwal rapat koordinasi minggu depan?", + senderId: userId, + divisionId: divisionIds[0] || null, + isResolved: true, + }, + { + message: "Rapat dijadwalkan hari Senin, 10:00 WITA", + senderId: userId, + divisionId: divisionIds[0] || null, + isResolved: true, + parentId: null, // Will be set as reply + }, + { + message: "Program pemberdayaan UMKM butuh anggaran tambahan", + senderId: userId, + divisionId: divisionIds[2] || null, + isResolved: false, + }, + ]; + + // Create parent discussions first + const parentDiscussions = []; + for (let i = 0; i < discussions.length; i += 2) { + const discussion = await prisma.discussion.create({ + data: { + message: discussions[i].message, + senderId: discussions[i].senderId, + divisionId: discussions[i].divisionId, + isResolved: discussions[i].isResolved, + }, + }); + parentDiscussions.push(discussion); + } + + // Create replies + for (let i = 1; i < discussions.length; i += 2) { + const parentIndex = Math.floor((i - 1) / 2); + if (parentIndex < parentDiscussions.length) { + await prisma.discussion.update({ + where: { id: parentDiscussions[parentIndex].id }, + data: { + replies: { + create: { + message: discussions[i].message, + senderId: discussions[i].senderId, + isResolved: discussions[i].isResolved, + }, + }, + }, + }); + } + } + + console.log("✅ Discussions seeded successfully"); +} + +/** + * Seed Division Metrics + * Creates performance metrics for each division + */ +export async function seedDivisionMetrics(divisionIds: string[]) { + console.log("Seeding Division Metrics..."); + + const metrics = [ + { + divisionId: divisionIds[0] || "", + period: "2025-Q1", + activityCount: 12, + completionRate: 75.5, + avgProgress: 82.3, + }, + { + divisionId: divisionIds[1] || "", + period: "2025-Q1", + activityCount: 8, + completionRate: 62.5, + avgProgress: 65.0, + }, + { + divisionId: divisionIds[2] || "", + period: "2025-Q1", + activityCount: 10, + completionRate: 80.0, + avgProgress: 70.5, + }, + { + divisionId: divisionIds[3] || "", + period: "2025-Q1", + activityCount: 15, + completionRate: 86.7, + avgProgress: 88.2, + }, + ]; + + for (const metric of metrics) { + await prisma.divisionMetric.upsert({ + where: { + divisionId_period: { + divisionId: metric.divisionId, + period: metric.period, + }, + }, + update: metric, + create: metric, + }); + } + + console.log("✅ Division Metrics seeded successfully"); +} diff --git a/prisma/seeders/seed-phase2.ts b/prisma/seeders/seed-phase2.ts new file mode 100644 index 0000000..11137da --- /dev/null +++ b/prisma/seeders/seed-phase2.ts @@ -0,0 +1,254 @@ +import { PrismaClient } from "../../generated/prisma"; + +const prisma = new PrismaClient(); + +/** + * Seed UMKM (Usaha Mikro, Kecil, dan Menengah) + * Creates sample local businesses for each banjar + */ +export async function seedUmkm(banjarIds: string[]) { + console.log("Seeding UMKM..."); + + const umkms = [ + { + banjarId: banjarIds[0] || null, + name: "Kerajinan Anyaman Darmasaba", + owner: "Ni Wayan Rajin", + productType: "Kerajinan Tangan", + description: "Produksi anyasan bambu dan rotan", + }, + { + banjarId: banjarIds[1] || null, + name: "Warung Makan Manesa", + owner: "Made Sari", + productType: "Kuliner", + description: "Makanan tradisional Bali", + }, + { + banjarId: banjarIds[2] || null, + name: "Bengkel Cabe Motor", + owner: "Ketut Arsana", + productType: "Jasa", + description: "Servis motor dan jual sparepart", + }, + { + banjarId: banjarIds[3] || null, + name: "Produksi Keripik Pisang Penenjoan", + owner: "Putu Suartika", + productType: "Makanan Ringan", + description: "Keripik pisang dengan berbagai varian rasa", + }, + ]; + + for (const umkm of umkms) { + await prisma.umkm.create({ + data: umkm, + }); + } + + console.log("✅ UMKM seeded successfully"); +} + +/** + * Seed Posyandu (Community Health Post) + * Creates health service schedules and programs + */ +export async function seedPosyandu(userId: string) { + console.log("Seeding Posyandu..."); + + const posyandus = [ + { + name: "Posyandu Mawar", + location: "Banjar Darmasaba", + schedule: "Setiap tanggal 15", + type: "Ibu dan Anak", + coordinatorId: userId, + }, + { + name: "Posyandu Melati", + location: "Banjar Manesa", + schedule: "Setiap tanggal 20", + type: "Ibu dan Anak", + coordinatorId: userId, + }, + { + name: "Posyandu Lansia Sejahtera", + location: "Balai Desa", + schedule: "Setiap tanggal 25", + type: "Lansia", + coordinatorId: userId, + }, + ]; + + for (const posyandu of posyandus) { + await prisma.posyandu.create({ + data: posyandu, + }); + } + + console.log("✅ Posyandu seeded successfully"); +} + +/** + * Seed Security Reports + * Creates sample security incident reports + */ +export async function seedSecurityReports(userId: string) { + console.log("Seeding Security Reports..."); + + const securityReports = [ + { + reportNumber: "SEC-2025-001", + title: "Pencurian Kendaraan", + description: "Laporan kehilangan motor di area pasar", + location: "Pasar Darmasaba", + reportedBy: "I Wayan Aman", + status: "DIPROSES", + assignedTo: userId, + }, + { + reportNumber: "SEC-2025-002", + title: "Gangguan Ketertiban", + description: "Keributan di jalan utama pada malam hari", + location: "Jl. Raya Darmasaba", + reportedBy: "Made Tertib", + status: "SELESAI", + assignedTo: userId, + }, + ]; + + for (const report of securityReports) { + await prisma.securityReport.upsert({ + where: { reportNumber: report.reportNumber }, + update: report, + create: report, + }); + } + + console.log("✅ Security Reports seeded successfully"); +} + +/** + * Seed Employment Records + * Creates employment history for residents + */ +export async function seedEmploymentRecords() { + console.log("Seeding Employment Records..."); + + // Get residents first + const residents = await prisma.resident.findMany({ + take: 2, + }); + + if (residents.length === 0) { + console.log("⏭️ No residents found, skipping employment records"); + return; + } + + const employmentRecords = residents.map((resident) => ({ + residentId: resident.id, + companyName: `PT. Desa Makmur ${resident.name.split(" ")[0]}`, + position: "Staff", + startDate: new Date("2020-01-01"), + endDate: null, + isActive: true, + })); + + for (const record of employmentRecords) { + await prisma.employmentRecord.create({ + data: record, + }); + } + + console.log("✅ Employment Records seeded successfully"); +} + +/** + * Seed Population Dynamics + * Creates population change records (births, deaths, migration) + */ +export async function seedPopulationDynamics(userId: string) { + console.log("Seeding Population Dynamics..."); + + const populationDynamics = [ + { + type: "KELAHIRAN", + residentName: "Anak Baru Darmasaba", + eventDate: new Date("2025-01-15"), + description: "Kelahiran bayi laki-laki", + documentedBy: userId, + }, + { + type: "KEMATIAN", + residentName: "Almarhum Warga Desa", + eventDate: new Date("2025-02-20"), + description: "Meninggal dunia karena sakit", + documentedBy: userId, + }, + { + type: "KEDATANGAN", + residentName: "Pendatang Baru", + eventDate: new Date("2025-03-01"), + description: "Pindah masuk dari desa lain", + documentedBy: userId, + }, + ]; + + for (const dynamic of populationDynamics) { + await prisma.populationDynamic.create({ + data: dynamic, + }); + } + + console.log("✅ Population Dynamics seeded successfully"); +} + +/** + * Seed Budget Transactions + * Creates sample financial transactions + */ +export async function seedBudgetTransactions(userId: string) { + console.log("Seeding Budget Transactions..."); + + const transactions = [ + { + transactionNumber: "TRX-2025-001", + type: "PENGELUARAN", + category: "Infrastruktur", + amount: 50000000, + description: "Pembangunan jalan desa", + date: new Date("2025-01-10"), + createdBy: userId, + }, + { + transactionNumber: "TRX-2025-002", + type: "PENDAPATAN", + category: "Dana Desa", + amount: 500000000, + description: "Penyaluran dana desa Q1", + date: new Date("2025-01-05"), + createdBy: userId, + }, + ]; + + for (const transaction of transactions) { + await prisma.budgetTransaction.create({ + data: transaction, + }); + } + + console.log("✅ Budget Transactions seeded successfully"); +} + +/** + * Seed All Phase 2 Data + * Main function to run all Phase 2 seeders + */ +export async function seedPhase2(banjarIds: string[], userId: string) { + await seedUmkm(banjarIds); + await seedPosyandu(userId); + await seedSecurityReports(userId); + await seedEmploymentRecords(); + await seedPopulationDynamics(userId); + await seedBudgetTransactions(userId); +} diff --git a/prisma/seeders/seed-public-services.ts b/prisma/seeders/seed-public-services.ts index b012f9b..a409fc2 100644 --- a/prisma/seeders/seed-public-services.ts +++ b/prisma/seeders/seed-public-services.ts @@ -8,6 +8,15 @@ import { const prisma = new PrismaClient(); +/** + * Get Complaint IDs + * Helper function to retrieve complaint IDs for other seeders + */ +export async function getComplaintIds(): Promise { + const complaints = await prisma.complaint.findMany(); + return complaints.map((c) => c.id); +} + /** * Seed Complaints * Creates sample citizen complaints for testing @@ -172,3 +181,39 @@ export async function seedInnovationIdeas(adminId: string) { console.log("✅ Innovation Ideas seeded successfully"); } + +/** + * Seed Complaint Updates + * Creates status update history for complaints + */ +export async function seedComplaintUpdates(complaintIds: string[], userId: string) { + console.log("Seeding Complaint Updates..."); + + if (complaintIds.length === 0) { + console.log("⏭️ No complaints found, skipping updates"); + return; + } + + const updates = [ + { + complaintId: complaintIds[0], + message: "Laporan diterima, akan segera ditindaklanjuti", + status: ComplaintStatus.BARU, + updatedBy: userId, + }, + { + complaintId: complaintIds[1], + message: "Tim kebersihan telah dikirim ke lokasi", + status: ComplaintStatus.DIPROSES, + updatedBy: userId, + }, + ]; + + for (const update of updates) { + await prisma.complaintUpdate.create({ + data: update, + }); + } + + console.log("✅ Complaint Updates seeded successfully"); +}