[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

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