[darmasaba-dashboard][2026-03-27] feat: complete all seeders and update Phase 2 schema

Schema Updates:
- Added fields to Umkm model (name, owner, productType, description, timestamps)
- Added fields to Posyandu model (name, location, schedule, type, timestamps)
- Added fields to SecurityReport model (reportNumber, title, description, location, reportedBy, status, timestamps)
- Added fields to EmploymentRecord model (companyName, position, startDate, endDate, isActive, timestamps)
- Added fields to PopulationDynamic model (type, residentName, eventDate, description, timestamps)
- Added fields to BudgetTransaction model (transactionNumber, type, category, amount, description, date, timestamps)
- Added fields to HealthRecord model (type, notes, timestamps)

New Seeders:
- seed-discussions.ts: Documents, Discussions, DivisionMetrics
- seed-phase2.ts: UMKM, Posyandu, SecurityReports, EmploymentRecords, PopulationDynamics, BudgetTransactions

Enhanced Seeders:
- seed-auth.ts: Added seedApiKeys() function
- seed-public-services.ts: Added seedComplaintUpdates() and getComplaintIds()

New NPM Scripts:
- seed:documents - Seed documents and discussions
- seed:phase2 - Seed Phase 2+ features

All 33 Prisma models now have seeder coverage (82% direct, 12% stubs, 6% auto-managed)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-03-27 14:05:15 +08:00
parent 44b6b158ef
commit c216fa074d
10 changed files with 762 additions and 33 deletions

View File

@@ -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",

View File

@@ -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;

View File

@@ -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");

View File

@@ -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");

View File

@@ -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 ---

View File

@@ -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);
}

View File

@@ -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");
}

View File

@@ -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");
}

View File

@@ -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);
}

View File

@@ -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<string[]> {
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");
}