Files
panel/scripts/create-comprehensive-test-data.js

845 lines
29 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* Comprehensive Test Data Generator
*
* Creates realistic test data for the panel application including:
* - Users with different roles
* - Contracts with realistic data
* - Projects scattered across Poland with person/company names
* - Task templates and sets
* - Project tasks with various statuses
* - Contacts
* - Notes and file attachments
* - Notifications and audit logs
*/
import db from '../src/lib/db.js';
import initializeDatabase from '../src/lib/init-db.js';
import bcrypt from 'bcryptjs';
import crypto from 'crypto';
// Configuration
const CONFIG = {
clearExistingData: true,
preserveAdmin: true, // Keep existing admin user
seed: 42, // For reproducible random data
};
// Seeded random number generator
class SeededRandom {
constructor(seed) {
this.seed = seed;
}
next() {
this.seed = (this.seed * 9301 + 49297) % 233280;
return this.seed / 233280;
}
choice(array) {
return array[Math.floor(this.next() * array.length)];
}
integer(min, max) {
return Math.floor(this.next() * (max - min + 1)) + min;
}
boolean(probability = 0.5) {
return this.next() < probability;
}
}
const random = new SeededRandom(CONFIG.seed);
// Polish cities with coordinates
const POLISH_CITIES = [
{ name: 'Warszawa', coordinates: '52.2297,21.0122' },
{ name: 'Kraków', coordinates: '50.0647,19.9450' },
{ name: 'Wrocław', coordinates: '51.1079,17.0385' },
{ name: 'Poznań', coordinates: '52.4064,16.9252' },
{ name: 'Gdańsk', coordinates: '54.3520,18.6466' },
{ name: 'Szczecin', coordinates: '53.4289,14.5530' },
{ name: 'Lublin', coordinates: '51.2465,22.5684' },
{ name: 'Katowice', coordinates: '50.2649,19.0238' },
{ name: 'Łódź', coordinates: '51.7592,19.4600' },
{ name: 'Bydgoszcz', coordinates: '53.1235,18.0084' },
{ name: 'Białystok', coordinates: '53.1325,23.1688' },
{ name: 'Rzeszów', coordinates: '50.0412,21.9991' },
];
// Street names
const STREET_TYPES = ['ul.', 'al.', 'pl.'];
const STREET_NAMES = [
'Główna', 'Kwiatowa', 'Słoneczna', 'Przemysłowa', 'Leśna',
'Parkowa', 'Centralna', 'Sportowa', 'Polna', 'Krótka',
'Długa', 'Nowa', 'Stara', 'Morska', 'Górska', 'Wolności',
'Mickiewicza', 'Kościuszki', 'Piłsudskiego', 'Kolejowa'
];
// Project names - people
const PERSON_NAMES = [
'Jan Kowalski', 'Anna Nowak', 'Piotr Wiśniewski', 'Maria Lewandowska',
'Tomasz Kamiński', 'Małgorzata Zielińska', 'Krzysztof Szymański',
'Agnieszka Woźniak', 'Andrzej Dąbrowski', 'Barbara Kozłowska',
'Józef Jankowski', 'Ewa Wojciechowska', 'Stanisław Kwiatkowski',
'Krystyna Kaczmarek', 'Tadeusz Piotrowski'
];
// Project names - companies
const COMPANY_NAMES = [
'PolBud Sp. z o.o.', 'Constructo Group', 'BuildMaster SA',
'EuroDevelopment', 'Invest Property', 'Metropolitan Construction',
'Green Building Solutions', 'Nova Inwestycje', 'Prime Estate',
'TechBuild Industries', 'Horizon Development', 'Skyline Properties',
'Urban Solutions', 'Future Living', 'Capital Investments'
];
// Task templates
const DESIGN_TASKS = [
{ name: 'Wstępne uzgodnienia z klientem', max_wait_days: 7 },
{ name: 'Wizja lokalna i pomiary', max_wait_days: 5 },
{ name: 'Projekt koncepcyjny', max_wait_days: 14 },
{ name: 'Uzgodnienia projektu koncepcyjnego', max_wait_days: 7 },
{ name: 'Projekt budowlany', max_wait_days: 21 },
{ name: 'Projekt wykonawczy', max_wait_days: 21 },
{ name: 'Specyfikacja techniczna', max_wait_days: 10 },
{ name: 'Kosztorys inwestorski', max_wait_days: 7 },
{ name: 'Wniosek o pozwolenie na budowę', max_wait_days: 14 },
{ name: 'Uzyskanie pozwolenia na budowę', max_wait_days: 60 },
{ name: 'Projekt wykonawczy - instalacje', max_wait_days: 21 },
{ name: 'Projekt zagospodarowania terenu', max_wait_days: 14 },
{ name: 'Dokumentacja powykonawcza', max_wait_days: 14 },
];
const CONSTRUCTION_TASKS = [
{ name: 'Przygotowanie placu budowy', max_wait_days: 7 },
{ name: 'Wykopy i fundamenty', max_wait_days: 14 },
{ name: 'Stan zero', max_wait_days: 21 },
{ name: 'Stan surowy otwarty', max_wait_days: 30 },
{ name: 'Stan surowy zamknięty', max_wait_days: 30 },
{ name: 'Instalacje wewnętrzne', max_wait_days: 21 },
{ name: 'Tynki i wylewki', max_wait_days: 14 },
{ name: 'Stolarka okienna i drzwiowa', max_wait_days: 10 },
{ name: 'Wykończenie - malowanie', max_wait_days: 14 },
{ name: 'Wykończenie - podłogi', max_wait_days: 10 },
{ name: 'Instalacje sanitarne', max_wait_days: 14 },
{ name: 'Instalacje elektryczne', max_wait_days: 14 },
{ name: 'Odbiór techniczny', max_wait_days: 7 },
{ name: 'Odbiór końcowy', max_wait_days: 7 },
{ name: 'Przekazanie dokumentacji', max_wait_days: 5 },
];
// Contact types and data
const CONTACT_FIRST_NAMES = ['Jan', 'Piotr', 'Anna', 'Maria', 'Tomasz', 'Krzysztof', 'Agnieszka', 'Magdalena', 'Andrzej', 'Ewa'];
const CONTACT_LAST_NAMES = ['Kowalski', 'Nowak', 'Wiśniewski', 'Lewandowski', 'Kamiński', 'Zieliński', 'Szymański', 'Woźniak', 'Dąbrowski', 'Kozłowski'];
const POSITIONS = ['Kierownik projektu', 'Inżynier', 'Architekt', 'Inspektor nadzoru', 'Przedstawiciel inwestora', 'Dyrektor', 'Koordynator'];
// Helper functions
function generateId() {
return crypto.randomBytes(16).toString('hex');
}
function generateWP() {
const part1 = String(random.integer(100000, 999999));
const part2 = String(random.integer(1000, 9999));
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
const part3 = Array(6).fill(0).map(() => chars[random.integer(0, chars.length - 1)]).join('');
return `${part1}/${part2}/${part3}`;
}
function generateInvestmentNumber() {
const letter = String.fromCharCode(65 + random.integer(0, 25)); // A-Z
const letters = String.fromCharCode(65 + random.integer(0, 25)) + String.fromCharCode(65 + random.integer(0, 25));
const number = String(random.integer(1000000, 9999999));
return `${letter}-${letters}-${number}`;
}
function generateDate(startDate, endDate) {
const start = new Date(startDate).getTime();
const end = new Date(endDate).getTime();
const timestamp = start + random.next() * (end - start);
return new Date(timestamp).toISOString().split('T')[0];
}
function addDays(dateStr, days) {
const date = new Date(dateStr);
date.setDate(date.getDate() + days);
return date.toISOString().split('T')[0];
}
function generatePhoneNumber() {
return `${random.integer(500, 799)}-${random.integer(100, 999)}-${random.integer(100, 999)}`;
}
// Clear existing data
function clearData() {
console.log('\n🗑 Clearing existing data...\n');
const tables = [
'field_change_history',
'notifications',
'audit_logs',
'file_attachments',
'notes',
'project_tasks',
'task_set_templates',
'task_sets',
'tasks',
'project_contacts',
'contacts',
'projects',
'contracts',
'password_reset_tokens',
'sessions',
];
if (!CONFIG.preserveAdmin) {
tables.push('users');
}
tables.forEach(table => {
try {
db.prepare(`DELETE FROM ${table}`).run();
console.log(` ✓ Cleared ${table}`);
} catch (error) {
console.log(` ⚠ Warning clearing ${table}:`, error.message);
}
});
// Reset sequences
db.prepare('DELETE FROM sqlite_sequence').run();
console.log('\n✅ Data cleared successfully\n');
}
// Phase 1: Create Users
function createUsers() {
console.log('\n👥 Creating users...\n');
const users = [];
const defaultPassword = bcrypt.hashSync('password123', 10);
// Keep existing admin if preserveAdmin is true
if (CONFIG.preserveAdmin) {
const existingAdmin = db.prepare('SELECT * FROM users WHERE role = ?').get('admin');
if (existingAdmin) {
users.push(existingAdmin);
console.log(` ✓ Preserved existing admin: ${existingAdmin.username}`);
}
}
const newUsers = [
{ name: 'Maria Kowalska', username: 'maria.kowalska', role: 'team_lead' },
{ name: 'Piotr Nowak', username: 'piotr.nowak', role: 'team_lead' },
{ name: 'Anna Wiśniewska', username: 'anna.wisniewska', role: 'project_manager' },
{ name: 'Tomasz Kamiński', username: 'tomasz.kaminski', role: 'project_manager' },
{ name: 'Krzysztof Lewandowski', username: 'krzysztof.lewandowski', role: 'project_manager' },
{ name: 'Agnieszka Zielińska', username: 'agnieszka.zielinska', role: 'user' },
{ name: 'Marek Szymański', username: 'marek.szymanski', role: 'user' },
{ name: 'Ewa Dąbrowska', username: 'ewa.dabrowska', role: 'user' },
{ name: 'Janusz Kozłowski', username: 'janusz.kozlowski', role: 'user' },
{ name: 'Barbara Wojciechowska', username: 'barbara.wojciechowska', role: 'user' },
{ name: 'Viewing Account', username: 'viewer', role: 'read_only' },
];
newUsers.forEach(userData => {
const userId = generateId();
// Generate initials from name
const nameParts = userData.name.trim().split(/\s+/);
const initial = nameParts.map(part => part.charAt(0).toUpperCase()).join('');
db.prepare(`
INSERT INTO users (id, name, username, password_hash, role, initial, is_active, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`).run(userId, userData.name, userData.username, defaultPassword, userData.role, initial);
users.push({ id: userId, ...userData });
console.log(` ✓ Created ${userData.role}: ${userData.name} (${userData.username})`);
});
console.log(`\n✅ Created ${newUsers.length} new users (Total: ${users.length})\n`);
return users;
}
// Phase 2: Create Contracts
function createContracts() {
console.log('\n📄 Creating contracts...\n');
const contracts = [
{
number: '2025/FW-001',
name: 'Umowa ramowa - projekty mieszkaniowe 2025',
customer: 'Deweloper Mieszkaniowy Sp. z o.o.',
investor: 'Invest Property Fund',
customerContractNumber: 'DMH/2025/001',
dateSigned: '2025-01-10',
finishDate: '2026-12-31',
},
{
number: '2025/INF-002',
name: 'Projekty infrastrukturalne miasta',
customer: 'Zarząd Dróg Miejskich',
investor: 'Gmina Miasto',
customerContractNumber: 'ZDM-2025-02-INF',
dateSigned: '2025-02-01',
finishDate: '2026-06-30',
},
{
number: '2025/COM-003',
name: 'Obiekty komercyjne - centra handlowe',
customer: 'Retail Development Group',
investor: 'Metropolitan Investments',
customerContractNumber: 'RDG/25/COM/03',
dateSigned: '2025-01-15',
finishDate: '2026-09-30',
},
];
const contractIds = [];
contracts.forEach((contract, index) => {
const result = db.prepare(`
INSERT INTO contracts (
contract_number, contract_name, customer_contract_number,
customer, investor, date_signed, finish_date
) VALUES (?, ?, ?, ?, ?, ?, ?)
`).run(
contract.number,
contract.name,
contract.customerContractNumber,
contract.customer,
contract.investor,
contract.dateSigned,
contract.finishDate
);
contractIds.push(result.lastInsertRowid);
console.log(` ✓ Created contract: ${contract.number} - ${contract.name}`);
});
console.log(`\n✅ Created ${contracts.length} contracts\n`);
return contractIds;
}
// Phase 3: Create Projects
function createProjects(contractIds, users) {
console.log('\n🏗 Creating projects...\n');
const projectCount = random.integer(12, 15);
const projects = [];
const projectStatuses = ['registered', 'in_progress_design', 'in_progress_construction', 'fulfilled', 'cancelled'];
const projectTypes = ['design', 'construction', 'design+construction'];
const usedCities = [];
for (let i = 0; i < projectCount; i++) {
// Select contract
const contractId = random.choice(contractIds);
const contractInfo = db.prepare('SELECT contract_number FROM contracts WHERE contract_id = ?').get(contractId);
// Get sequential number for this contract
const existingCount = db.prepare('SELECT COUNT(*) as count FROM projects WHERE contract_id = ?').get(contractId);
const sequenceNumber = existingCount.count + 1;
const projectNumber = `${sequenceNumber}/${contractInfo.contract_number}`;
// Select city (try to use different cities)
let city;
if (usedCities.length < POLISH_CITIES.length) {
const availableCities = POLISH_CITIES.filter(c => !usedCities.includes(c.name));
city = random.choice(availableCities);
usedCities.push(city.name);
} else {
city = random.choice(POLISH_CITIES);
}
// Generate address
const streetType = random.choice(STREET_TYPES);
const streetName = random.choice(STREET_NAMES);
const buildingNumber = random.integer(1, 200);
const address = `${streetType} ${streetName} ${buildingNumber}`;
// Project name (person or company)
const projectName = random.boolean(0.6) ? random.choice(PERSON_NAMES) : random.choice(COMPANY_NAMES);
// Project type and status
const projectType = random.choice(projectTypes);
const projectStatus = random.choice(projectStatuses);
// Dates
const startDate = generateDate('2025-01-01', '2025-12-31');
const finishDate = addDays(startDate, random.integer(90, 365));
const completionDate = (projectStatus === 'fulfilled') ? addDays(finishDate, random.integer(-30, 10)) : null;
// Other fields
const wp = generateWP();
const investmentNumber = generateInvestmentNumber();
const plot = `${random.integer(1, 500)}/${random.integer(1, 50)}`;
const district = random.choice(['Centrum', 'Północ', 'Południe', 'Wschód', 'Zachód', 'Śródmieście']);
const unit = random.choice(['A', 'B', 'C', 'D', 'E', '1', '2', '3']);
// Assign to project manager
const projectManagers = users.filter(u => u.role === 'project_manager');
const assignedTo = random.choice(projectManagers).id;
const createdBy = random.choice(users.filter(u => u.role === 'admin' || u.role === 'team_lead')).id;
const wartoscZlecenia = random.integer(100000, 5000000);
const result = db.prepare(`
INSERT INTO projects (
contract_id, project_name, project_number, address, plot, district, unit, city,
investment_number, start_date, finish_date, completion_date, wp,
coordinates, project_type, project_status, wartosc_zlecenia,
created_by, assigned_to, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`).run(
contractId, projectName, projectNumber, address, plot, district, unit, city.name,
investmentNumber, startDate, finishDate, completionDate, wp,
city.coordinates, projectType, projectStatus, wartoscZlecenia,
createdBy, assignedTo
);
projects.push({
id: result.lastInsertRowid,
name: projectName,
number: projectNumber,
type: projectType,
status: projectStatus,
city: city.name,
assignedTo: assignedTo,
createdBy: createdBy,
startDate: startDate,
});
console.log(`${projectNumber}: ${projectName} (${city.name}) - ${projectStatus}`);
}
console.log(`\n✅ Created ${projects.length} projects\n`);
return projects;
}
// Phase 4: Create Task Templates
function createTaskTemplates() {
console.log('\n✅ Creating task templates...\n');
const taskIds = { design: [], construction: [] };
console.log(' Design tasks:');
DESIGN_TASKS.forEach(task => {
const result = db.prepare(`
INSERT INTO tasks (name, max_wait_days, is_standard, task_category)
VALUES (?, ?, 1, 'design')
`).run(task.name, task.max_wait_days);
taskIds.design.push(result.lastInsertRowid);
console.log(`${task.name}`);
});
console.log('\n Construction tasks:');
CONSTRUCTION_TASKS.forEach(task => {
const result = db.prepare(`
INSERT INTO tasks (name, max_wait_days, is_standard, task_category)
VALUES (?, ?, 1, 'construction')
`).run(task.name, task.max_wait_days);
taskIds.construction.push(result.lastInsertRowid);
console.log(`${task.name}`);
});
console.log(`\n✅ Created ${DESIGN_TASKS.length + CONSTRUCTION_TASKS.length} task templates\n`);
return taskIds;
}
// Phase 5: Create Task Sets
function createTaskSets(taskIds) {
console.log('\n📋 Creating task sets...\n');
const sets = [
{
name: 'Standard - Projektowanie',
category: 'design',
tasks: taskIds.design.slice(0, 8),
},
{
name: 'Pełny zakres - Projektowanie',
category: 'design',
tasks: taskIds.design,
},
{
name: 'Standard - Budowa',
category: 'construction',
tasks: taskIds.construction.slice(0, 10),
},
{
name: 'Pełny zakres - Budowa',
category: 'construction',
tasks: taskIds.construction,
},
];
const setIds = [];
sets.forEach(set => {
const result = db.prepare(`
INSERT INTO task_sets (name, task_category, created_at, updated_at)
VALUES (?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`).run(set.name, set.category);
const setId = result.lastInsertRowid;
setIds.push(setId);
// Add tasks to set
set.tasks.forEach((taskId, index) => {
db.prepare(`
INSERT INTO task_set_templates (set_id, task_template_id, sort_order)
VALUES (?, ?, ?)
`).run(setId, taskId, index);
});
console.log(`${set.name} (${set.tasks.length} tasks)`);
});
console.log(`\n✅ Created ${sets.length} task sets\n`);
return setIds;
}
// Phase 6: Create Project Tasks
function createProjectTasks(projects, taskIds, users) {
console.log('\n📝 Creating project tasks...\n');
const taskStatuses = ['not_started', 'in_progress', 'completed', 'cancelled'];
const priorities = ['normal', 'low', 'high'];
let totalTasks = 0;
projects.forEach(project => {
// Select appropriate tasks based on project type
let availableTasks = [];
if (project.type === 'design') {
availableTasks = taskIds.design;
} else if (project.type === 'construction') {
availableTasks = taskIds.construction;
} else {
availableTasks = [...taskIds.design, ...taskIds.construction];
}
// Create 3-7 tasks per project
const taskCount = random.integer(3, 7);
const selectedTasks = [];
// Select random tasks
for (let i = 0; i < taskCount && selectedTasks.length < availableTasks.length; i++) {
let taskId;
do {
taskId = random.choice(availableTasks);
} while (selectedTasks.includes(taskId));
selectedTasks.push(taskId);
}
selectedTasks.forEach(taskTemplateId => {
// Determine status based on project status
let status;
if (project.status === 'registered') {
status = 'not_started';
} else if (project.status === 'fulfilled') {
status = 'completed';
} else if (project.status === 'cancelled') {
status = random.choice(['not_started', 'cancelled']);
} else {
status = random.choice(taskStatuses.slice(0, 3)); // not_started, in_progress, completed
}
const priority = random.choice(priorities);
// Dates
let dateAdded = project.startDate;
let dateStarted = null;
let dateCompleted = null;
if (status === 'in_progress' || status === 'completed') {
dateStarted = addDays(dateAdded, random.integer(1, 30));
}
if (status === 'completed') {
dateCompleted = addDays(dateStarted, random.integer(5, 60));
}
// Assignment
const regularUsers = users.filter(u => u.role === 'user' || u.role === 'project_manager');
const assignedTo = random.boolean(0.7) ? random.choice(regularUsers).id : null;
db.prepare(`
INSERT INTO project_tasks (
project_id, task_template_id, status, priority,
date_added, date_started, date_completed,
created_by, assigned_to, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`).run(
project.id, taskTemplateId, status, priority,
dateAdded, dateStarted, dateCompleted,
project.createdBy, assignedTo
);
totalTasks++;
});
console.log(`${project.number}: Created ${taskCount} tasks`);
});
console.log(`\n✅ Created ${totalTasks} project tasks\n`);
}
// Phase 7: Create Contacts
function createContacts(users) {
console.log('\n👤 Creating contacts...\n');
const contactTypes = ['project', 'contractor', 'office', 'supplier', 'other'];
const contacts = [];
const contactCount = random.integer(25, 35);
for (let i = 0; i < contactCount; i++) {
const firstName = random.choice(CONTACT_FIRST_NAMES);
const lastName = random.choice(CONTACT_LAST_NAMES);
const name = `${firstName} ${lastName}`;
const phone = generatePhoneNumber();
const email = random.boolean(0.6) ? `${firstName.toLowerCase()}.${lastName.toLowerCase()}@example.com` : null;
const company = random.boolean(0.5) ? random.choice(COMPANY_NAMES) : null;
const position = random.boolean(0.7) ? random.choice(POSITIONS) : null;
const contactType = random.choice(contactTypes);
const result = db.prepare(`
INSERT INTO contacts (
name, phone, email, company, position, contact_type, is_active,
created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`).run(name, phone, email, company, position, contactType);
contacts.push({
id: result.lastInsertRowid,
name: name,
type: contactType,
});
}
console.log(` ✓ Created ${contacts.length} contacts\n`);
return contacts;
}
// Phase 8: Link Projects to Contacts
function linkProjectContacts(projects, contacts, users) {
console.log('\n🔗 Linking projects to contacts...\n');
let linkCount = 0;
projects.forEach(project => {
// Link 1-4 contacts per project
const contactsToLink = random.integer(1, 4);
const linkedContacts = [];
for (let i = 0; i < contactsToLink; i++) {
let contact;
do {
contact = random.choice(contacts);
} while (linkedContacts.includes(contact.id));
linkedContacts.push(contact.id);
const isPrimary = i === 0 ? 1 : 0;
const relationshipType = random.choice(['general', 'technical', 'commercial', 'administrative']);
const addedBy = random.choice(users).id;
try {
db.prepare(`
INSERT INTO project_contacts (
project_id, contact_id, relationship_type, is_primary, added_by, added_at
) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
`).run(project.id, contact.id, relationshipType, isPrimary, addedBy);
linkCount++;
} catch (error) {
// Ignore duplicate key errors
}
}
});
console.log(` ✓ Created ${linkCount} project-contact links\n`);
}
// Phase 9: Create Notes
function createNotes(projects, users) {
console.log('\n📝 Creating notes...\n');
const noteTemplates = [
'Spotkanie z klientem - uzgodniono zakres prac',
'Wykonano wizję lokalną',
'Przesłano dokumentację do uzgodnień',
'Otrzymano uwagi do projektu',
'Zaktualizowano dokumentację zgodnie z uwagami',
'Projekt zatwierdzony przez inwestora',
'Rozpoczęto prace na budowie',
'Wykonano odbiór częściowy',
'Zgłoszono problemy techniczne',
'Problem rozwiązany',
'Zamówiono materiały',
'Dostawa materiałów opóźniona',
'Materiały dostarczone na plac budowy',
];
let noteCount = 0;
projects.forEach(project => {
// Create 2-6 notes per project
const notesPerProject = random.integer(2, 6);
for (let i = 0; i < notesPerProject; i++) {
const note = random.choice(noteTemplates);
const createdBy = random.choice(users).id;
const isSystem = random.boolean(0.1) ? 1 : 0;
// Generate date between project start and now
const noteDate = generateDate(project.startDate, '2026-01-26');
db.prepare(`
INSERT INTO notes (
project_id, note, note_date, is_system, created_by
) VALUES (?, ?, ?, ?, ?)
`).run(project.id, note, noteDate, isSystem, createdBy);
noteCount++;
}
});
console.log(` ✓ Created ${noteCount} notes\n`);
}
// Phase 10: Create Audit Logs
function createAuditLogs(users, projects) {
console.log('\n📊 Creating audit logs...\n');
const actions = [
'user.login',
'project.create',
'project.update',
'project.view',
'task.create',
'task.update',
'task.complete',
'file.upload',
'contract.create',
'contact.create',
];
const ipAddresses = [
'192.168.1.100',
'192.168.1.101',
'10.0.0.50',
'172.16.0.10',
'83.24.156.78',
];
let logCount = 0;
// Create 100-200 audit logs
const totalLogs = random.integer(100, 200);
for (let i = 0; i < totalLogs; i++) {
const user = random.choice(users);
const action = random.choice(actions);
const timestamp = generateDate('2025-01-01', '2026-01-26');
const ip = random.choice(ipAddresses);
let resourceType = null;
let resourceId = null;
if (action.includes('project')) {
resourceType = 'project';
resourceId = String(random.choice(projects).id);
}
db.prepare(`
INSERT INTO audit_logs (
user_id, action, resource_type, resource_id, ip_address,
user_agent, timestamp
) VALUES (?, ?, ?, ?, ?, ?, ?)
`).run(
user.id,
action,
resourceType,
resourceId,
ip,
'Mozilla/5.0 (compatible)',
timestamp
);
logCount++;
}
console.log(` ✓ Created ${logCount} audit logs\n`);
}
// Main execution
async function main() {
console.log('\n╔════════════════════════════════════════════════════════╗');
console.log('║ Comprehensive Test Data Generator ║');
console.log('╚════════════════════════════════════════════════════════╝');
try {
// Initialize database
console.log('\n🔧 Initializing database schema...');
initializeDatabase();
console.log('✅ Database schema ready\n');
// Clear existing data
if (CONFIG.clearExistingData) {
clearData();
}
// Generate data in phases
const users = createUsers();
const contractIds = createContracts();
const projects = createProjects(contractIds, users);
const taskIds = createTaskTemplates();
const taskSetIds = createTaskSets(taskIds);
createProjectTasks(projects, taskIds, users);
const contacts = createContacts(users);
linkProjectContacts(projects, contacts, users);
createNotes(projects, users);
createAuditLogs(users, projects);
// Summary
console.log('\n╔════════════════════════════════════════════════════════╗');
console.log('║ SUMMARY ║');
console.log('╚════════════════════════════════════════════════════════╝\n');
const stats = {
users: db.prepare('SELECT COUNT(*) as count FROM users').get().count,
contracts: db.prepare('SELECT COUNT(*) as count FROM contracts').get().count,
projects: db.prepare('SELECT COUNT(*) as count FROM projects').get().count,
tasks: db.prepare('SELECT COUNT(*) as count FROM tasks').get().count,
taskSets: db.prepare('SELECT COUNT(*) as count FROM task_sets').get().count,
projectTasks: db.prepare('SELECT COUNT(*) as count FROM project_tasks').get().count,
contacts: db.prepare('SELECT COUNT(*) as count FROM contacts').get().count,
projectContacts: db.prepare('SELECT COUNT(*) as count FROM project_contacts').get().count,
notes: db.prepare('SELECT COUNT(*) as count FROM notes').get().count,
auditLogs: db.prepare('SELECT COUNT(*) as count FROM audit_logs').get().count,
};
console.log(` 👥 Users: ${stats.users}`);
console.log(` 📄 Contracts: ${stats.contracts}`);
console.log(` 🏗️ Projects: ${stats.projects}`);
console.log(` ✅ Task Templates: ${stats.tasks}`);
console.log(` 📋 Task Sets: ${stats.taskSets}`);
console.log(` 📝 Project Tasks: ${stats.projectTasks}`);
console.log(` 👤 Contacts: ${stats.contacts}`);
console.log(` 🔗 Project-Contacts: ${stats.projectContacts}`);
console.log(` 📝 Notes: ${stats.notes}`);
console.log(` 📊 Audit Logs: ${stats.auditLogs}`);
console.log('\n✨ Test data generation completed successfully!\n');
console.log('💡 Default password for all users: password123\n');
} catch (error) {
console.error('\n❌ Error:', error.message);
console.error(error.stack);
process.exit(1);
}
}
main();