@@ -0,0 +1,844 @@
#!/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 ( ) ;