- Backend: CRUD revízií, schedule endpoint (agregovaný plán), skip revízia, stats - Shared utility revisionSchedule.ts - centralizovaná logika výpočtu cyklov - Equipment detail s revíznym plánom, históriou a prílohami - Frontend: RevisionsList s tabmi (nadchádzajúce/po termíne/vykonané/preskočené) - Pozičné labeling cyklov (eliminuje drift 4×90≠365) - EquipmentRevisionSchedule model (many-to-many typy revízií) - Aktualizovaná dokumentácia HELPDESK_INIT_V2.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
717 lines
25 KiB
TypeScript
717 lines
25 KiB
TypeScript
import { PrismaClient } from '@prisma/client';
|
|
import bcrypt from 'bcryptjs';
|
|
import {
|
|
AUDIT_MAP,
|
|
PERIOD_TO_TYPE,
|
|
ADDITIONAL_EQUIP_TYPES,
|
|
CUSTOMERS,
|
|
EQUIPMENT,
|
|
SERVICE_RECORDS,
|
|
} from './seed-data';
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
/**
|
|
* Klasifikuje poznámku revízie na kód typu revízie.
|
|
* Staré dáta používali voľný text na zaznamenanie druhu revízie.
|
|
*/
|
|
function classifyNote(note: string, basePeriodicity: number): string {
|
|
const normalized = note.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
|
|
// Štvrťročná (rôzne varianty s preklepmi) - musí byť pred ročnou, lebo "stvrtrocna" obsahuje "rocn"
|
|
// s[trv]{1,4}rocn pokrýva: stvrtrocn, svrtrocn, strtrocn, strvtrocn a ďalšie preklepy
|
|
if (/s[trv]{1,4}rocn/.test(normalized)) return 'QUARTERLY';
|
|
// Ročná (vrátane preklepu "Ričná")
|
|
if (/rocn|ricn/.test(normalized)) return 'ANNUAL';
|
|
// Mesačná
|
|
if (/mesacn/.test(normalized)) return 'MONTHLY';
|
|
// Východzia / Vychodisková → základný typ z auditu
|
|
if (/vychodz|vychodisk/.test(normalized)) return PERIOD_TO_TYPE[basePeriodicity] || 'QUARTERLY';
|
|
// Default → typ z auditu
|
|
return PERIOD_TO_TYPE[basePeriodicity] || 'QUARTERLY';
|
|
}
|
|
|
|
/**
|
|
* Zistí, či poznámka indikuje "Východziu revíziu".
|
|
*/
|
|
function isVychodziaNote(note: string): boolean {
|
|
const normalized = note.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
return /vychodz|vychodisk/.test(normalized);
|
|
}
|
|
|
|
async function seed() {
|
|
console.log('Seeding database...');
|
|
|
|
// ===== USER ROLES =====
|
|
console.log('Creating user roles...');
|
|
const roles = await Promise.all([
|
|
prisma.userRole.upsert({
|
|
where: { code: 'ROOT' },
|
|
update: {},
|
|
create: {
|
|
code: 'ROOT',
|
|
name: 'Root Správca',
|
|
level: 1,
|
|
order: 1,
|
|
permissions: {
|
|
projects: ['*'],
|
|
tasks: ['*'],
|
|
equipment: ['*'],
|
|
rma: ['*'],
|
|
customers: ['*'],
|
|
settings: ['*'],
|
|
users: ['*'],
|
|
logs: ['*'],
|
|
},
|
|
},
|
|
}),
|
|
prisma.userRole.upsert({
|
|
where: { code: 'ADMIN' },
|
|
update: {},
|
|
create: {
|
|
code: 'ADMIN',
|
|
name: 'Administrátor',
|
|
level: 2,
|
|
order: 2,
|
|
permissions: {
|
|
projects: ['create', 'read', 'update', 'delete', 'all'],
|
|
tasks: ['create', 'read', 'update', 'delete', 'all'],
|
|
equipment: ['create', 'read', 'update', 'delete', 'all'],
|
|
rma: ['create', 'read', 'update', 'delete', 'approve'],
|
|
customers: ['create', 'read', 'update', 'delete'],
|
|
users: ['read'],
|
|
},
|
|
},
|
|
}),
|
|
prisma.userRole.upsert({
|
|
where: { code: 'USER' },
|
|
update: {},
|
|
create: {
|
|
code: 'USER',
|
|
name: 'Používateľ',
|
|
level: 3,
|
|
order: 3,
|
|
permissions: {
|
|
projects: ['read', 'update'],
|
|
tasks: ['create', 'read', 'update'],
|
|
equipment: ['read', 'update'],
|
|
rma: ['create', 'read', 'update'],
|
|
customers: ['read'],
|
|
},
|
|
},
|
|
}),
|
|
prisma.userRole.upsert({
|
|
where: { code: 'CUSTOMER' },
|
|
update: {},
|
|
create: {
|
|
code: 'CUSTOMER',
|
|
name: 'Zákazník',
|
|
level: 4,
|
|
order: 4,
|
|
permissions: {
|
|
projects: ['read'],
|
|
tasks: ['read'],
|
|
equipment: ['read'],
|
|
rma: ['create', 'read'],
|
|
},
|
|
},
|
|
}),
|
|
]);
|
|
|
|
// ===== EQUIPMENT TYPES =====
|
|
console.log('Creating equipment types...');
|
|
await prisma.equipmentType.createMany({
|
|
skipDuplicates: true,
|
|
data: [
|
|
{ code: 'EPS', name: 'Elektrická požiarna signalizácia', color: '#3B82F6', order: 1 },
|
|
{ code: 'HSP', name: 'Hlasová signalizácia požiaru', color: '#EF4444', order: 2 },
|
|
{ code: 'CAMERA', name: 'Kamerový systém', color: '#10B981', order: 3 },
|
|
{ code: 'ACCESS', name: 'Prístupový systém', color: '#F59E0B', order: 4 },
|
|
{ code: 'OTHER', name: 'Iné zariadenie', color: '#6B7280', order: 5 },
|
|
// Additional types from old DB
|
|
...ADDITIONAL_EQUIP_TYPES.map((t, i) => ({
|
|
code: t.code,
|
|
name: t.name,
|
|
color: t.color,
|
|
order: 6 + i,
|
|
})),
|
|
],
|
|
});
|
|
|
|
// ===== REVISION TYPES =====
|
|
console.log('Creating revision types...');
|
|
await prisma.revisionType.createMany({
|
|
skipDuplicates: true,
|
|
data: [
|
|
{ code: 'MONTHLY', name: 'Mesačná revízia', intervalDays: 30, reminderDays: 7, color: '#6366F1', order: 1 },
|
|
{ code: 'QUARTERLY', name: 'Štvrťročná revízia', intervalDays: 90, reminderDays: 14, color: '#FFA500', order: 2 },
|
|
{ code: 'BIANNUAL', name: 'Polročná revízia', intervalDays: 180, reminderDays: 21, color: '#FBBF24', order: 3 },
|
|
{ code: 'ANNUAL', name: 'Ročná revízia', intervalDays: 365, reminderDays: 30, color: '#DC2626', order: 4 },
|
|
{ code: 'BIENNIAL', name: 'Revízia každé 2 roky', intervalDays: 730, reminderDays: 60, color: '#8B5CF6', order: 5 },
|
|
{ code: 'TRIENNIAL', name: 'Revízia každé 3 roky', intervalDays: 1095, reminderDays: 90, color: '#0EA5E9', order: 6 },
|
|
{ code: 'QUADRENNIAL', name: 'Revízia každé 4 roky', intervalDays: 1460, reminderDays: 90, color: '#14B8A6', order: 7 },
|
|
{ code: 'QUINQUENNIAL', name: 'Revízia každých 5 rokov', intervalDays: 1825, reminderDays: 120, color: '#F97316', order: 8 },
|
|
{ code: 'EMERGENCY', name: 'Mimoriadna revízia', intervalDays: 0, reminderDays: 0, color: '#EF4444', order: 9 },
|
|
],
|
|
});
|
|
|
|
// ===== RMA STATUSES =====
|
|
console.log('Creating RMA statuses...');
|
|
await prisma.rMAStatus.createMany({
|
|
skipDuplicates: true,
|
|
data: [
|
|
{ code: 'NEW', name: 'Nová reklamácia', color: '#10B981', isInitial: true, canTransitionTo: ['IN_ASSESSMENT', 'REJECTED'], order: 1 },
|
|
{ code: 'IN_ASSESSMENT', name: 'V posúdzovaní', color: '#F59E0B', canTransitionTo: ['APPROVED', 'REJECTED'], order: 2 },
|
|
{ code: 'APPROVED', name: 'Schválená', color: '#3B82F6', canTransitionTo: ['IN_REPAIR', 'REPLACED', 'REFUNDED'], order: 3 },
|
|
{ code: 'REJECTED', name: 'Zamietnutá', color: '#EF4444', isFinal: true, order: 4 },
|
|
{ code: 'IN_REPAIR', name: 'V oprave', color: '#8B5CF6', canTransitionTo: ['REPAIRED', 'COMPLETED'], order: 5 },
|
|
{ code: 'REPAIRED', name: 'Opravené', color: '#059669', canTransitionTo: ['COMPLETED'], order: 6 },
|
|
{ code: 'REPLACED', name: 'Vymenené', color: '#059669', canTransitionTo: ['COMPLETED'], order: 7 },
|
|
{ code: 'REFUNDED', name: 'Vrátené peniaze', color: '#059669', canTransitionTo: ['COMPLETED'], order: 8 },
|
|
{ code: 'COMPLETED', name: 'Uzatvorená', color: '#059669', isFinal: true, order: 9 },
|
|
],
|
|
});
|
|
|
|
// ===== RMA SOLUTIONS =====
|
|
console.log('Creating RMA solutions...');
|
|
await prisma.rMASolution.createMany({
|
|
skipDuplicates: true,
|
|
data: [
|
|
{ code: 'ASSESSMENT', name: 'Posúdzovanie', color: '#F59E0B', order: 1 },
|
|
{ code: 'REPAIR', name: 'Oprava', color: '#3B82F6', order: 2 },
|
|
{ code: 'REPLACEMENT', name: 'Výmena', color: '#10B981', order: 3 },
|
|
{ code: 'REFUND', name: 'Vrátenie peňazí', color: '#8B5CF6', order: 4 },
|
|
{ code: 'REJECTED', name: 'Zamietnutie', color: '#EF4444', order: 5 },
|
|
{ code: 'OTHER', name: 'Iné riešenie', color: '#6B7280', order: 6 },
|
|
],
|
|
});
|
|
|
|
// ===== TASK STATUSES =====
|
|
console.log('Creating task statuses...');
|
|
await prisma.taskStatus.createMany({
|
|
skipDuplicates: true,
|
|
data: [
|
|
{ code: 'NEW', name: 'Nová úloha', swimlaneColumn: 'NEW', color: '#10B981', isInitial: true, order: 1 },
|
|
{ code: 'IN_PROGRESS', name: 'V riešení', swimlaneColumn: 'DOING', color: '#F59E0B', order: 2 },
|
|
{ code: 'REVIEW', name: 'Na kontrolu', swimlaneColumn: 'DOING', color: '#8B5CF6', order: 3 },
|
|
{ code: 'COMPLETED', name: 'Dokončená', swimlaneColumn: 'DONE', color: '#059669', isFinal: true, order: 4 },
|
|
],
|
|
});
|
|
|
|
// ===== PRIORITIES =====
|
|
console.log('Creating priorities...');
|
|
await prisma.priority.createMany({
|
|
skipDuplicates: true,
|
|
data: [
|
|
{ code: 'LOW', name: 'Nízka priorita', color: '#10B981', level: 1, order: 1 },
|
|
{ code: 'MEDIUM', name: 'Stredná priorita', color: '#F59E0B', level: 5, order: 2 },
|
|
{ code: 'HIGH', name: 'Vysoká priorita', color: '#EF4444', level: 8, order: 3 },
|
|
{ code: 'URGENT', name: 'Urgentná', color: '#DC2626', level: 10, order: 4 },
|
|
],
|
|
});
|
|
|
|
// ===== SYSTEM SETTINGS =====
|
|
console.log('Creating system settings...');
|
|
await prisma.systemSetting.createMany({
|
|
skipDuplicates: true,
|
|
data: [
|
|
{
|
|
key: 'REVISION_REMINDER_DAYS',
|
|
value: 14,
|
|
category: 'NOTIFICATIONS',
|
|
label: 'Pripomenúť revíziu X dní dopredu',
|
|
dataType: 'number',
|
|
validation: { min: 1, max: 365 },
|
|
},
|
|
{
|
|
key: 'RMA_NUMBER_FORMAT',
|
|
value: 'RMA-{YYYY}{MM}{DD}{XXX}',
|
|
category: 'RMA',
|
|
label: 'Formát RMA čísla',
|
|
dataType: 'string',
|
|
},
|
|
{
|
|
key: 'RMA_CUSTOMER_REQUIRES_APPROVAL',
|
|
value: true,
|
|
category: 'RMA',
|
|
label: 'Reklamácie od zákazníkov vyžadujú schválenie',
|
|
dataType: 'boolean',
|
|
},
|
|
{
|
|
key: 'ADMIN_NOTIFICATION_EMAILS',
|
|
value: ['admin@firma.sk'],
|
|
category: 'NOTIFICATIONS',
|
|
label: 'Email adresy pre admin notifikácie',
|
|
dataType: 'json',
|
|
},
|
|
{
|
|
key: 'ENABLE_WEBSOCKET',
|
|
value: false,
|
|
category: 'GENERAL',
|
|
label: 'Zapnúť real-time aktualizácie (WebSocket)',
|
|
dataType: 'boolean',
|
|
},
|
|
{
|
|
key: 'NOTIFICATION_SNOOZE_OPTIONS',
|
|
value: [
|
|
{ label: '30 minút', minutes: 30 },
|
|
{ label: '1 hodina', minutes: 60 },
|
|
{ label: '3 hodiny', minutes: 180 },
|
|
{ label: 'Dnes poobede o 13:00', type: 'today', hour: 13 },
|
|
{ label: 'Zajtra ráno o 8:00', type: 'tomorrow', hour: 8 },
|
|
{ label: 'Zajtra poobede o 13:00', type: 'tomorrow', hour: 13 },
|
|
],
|
|
category: 'NOTIFICATIONS',
|
|
label: 'Možnosti odloženia notifikácií',
|
|
description: 'Pole objektov. Každý má "label" a buď "minutes" (relatívny čas) alebo "type" + "hour" (konkrétny čas). Type: "today" (ak čas prešiel, skryje sa), "tomorrow".',
|
|
dataType: 'json',
|
|
},
|
|
{
|
|
key: 'REVISION_STATUS_THRESHOLDS',
|
|
value: [
|
|
{ days: 30, label: 'Blíži sa', color: '#EAB308' },
|
|
{ days: 14, label: 'Blíži sa!', color: '#F97316' },
|
|
{ days: 7, label: 'Urgentné!', color: '#EF4444' },
|
|
],
|
|
category: 'REVISIONS',
|
|
label: 'Prahy upozornení revízií',
|
|
description: 'Pole prahov zoradených od najväčšieho po najmenší počet dní. Každý prah má: "days" (počet dní pred termínom), "label" (text), "color" (hex farba). Po termíne sa vždy zobrazuje červená.',
|
|
dataType: 'json',
|
|
},
|
|
],
|
|
});
|
|
|
|
// ===== USERS (demo + real from old DB) =====
|
|
console.log('Creating users...');
|
|
const rootRole = roles.find(r => r.code === 'ROOT');
|
|
const adminRole = roles.find(r => r.code === 'ADMIN');
|
|
const userRole = roles.find(r => r.code === 'USER');
|
|
const customerRole = roles.find(r => r.code === 'CUSTOMER');
|
|
|
|
if (!rootRole || !adminRole || !userRole || !customerRole) {
|
|
throw new Error('Required roles not found');
|
|
}
|
|
|
|
const [hashedPassword, hashedRoot, hashedAdmin, hashedUser, hashedZakaznik] = await Promise.all([
|
|
bcrypt.hash('heslo123', 10),
|
|
bcrypt.hash('root123', 10),
|
|
bcrypt.hash('admin123', 10),
|
|
bcrypt.hash('user123', 10),
|
|
bcrypt.hash('zakaznik123', 10),
|
|
]);
|
|
|
|
const rootUser = await prisma.user.upsert({
|
|
where: { email: 'root@helpdesk.sk' },
|
|
update: {},
|
|
create: {
|
|
email: 'root@helpdesk.sk',
|
|
password: hashedPassword,
|
|
name: 'Root Admin',
|
|
roleId: rootRole.id,
|
|
},
|
|
});
|
|
|
|
// Demo users - jeden pre každú rolu
|
|
const [demoRoot, demoAdmin, user1, user2, user3, demoZakaznik] = await Promise.all([
|
|
prisma.user.upsert({
|
|
where: { email: 'root@root.sk' },
|
|
update: {},
|
|
create: {
|
|
email: 'root@root.sk',
|
|
password: hashedRoot,
|
|
name: 'Root Používateľ',
|
|
roleId: rootRole.id,
|
|
},
|
|
}),
|
|
prisma.user.upsert({
|
|
where: { email: 'admin@admin.sk' },
|
|
update: {},
|
|
create: {
|
|
email: 'admin@admin.sk',
|
|
password: hashedAdmin,
|
|
name: 'Admin Používateľ',
|
|
roleId: adminRole.id,
|
|
},
|
|
}),
|
|
prisma.user.upsert({
|
|
where: { email: 'user1@user.sk' },
|
|
update: {},
|
|
create: {
|
|
email: 'user1@user.sk',
|
|
password: hashedUser,
|
|
name: 'Ján Technik',
|
|
roleId: userRole.id,
|
|
},
|
|
}),
|
|
prisma.user.upsert({
|
|
where: { email: 'user2@user.sk' },
|
|
update: {},
|
|
create: {
|
|
email: 'user2@user.sk',
|
|
password: hashedUser,
|
|
name: 'Peter Sieťar',
|
|
roleId: userRole.id,
|
|
},
|
|
}),
|
|
prisma.user.upsert({
|
|
where: { email: 'user3@user.sk' },
|
|
update: {},
|
|
create: {
|
|
email: 'user3@user.sk',
|
|
password: hashedUser,
|
|
name: 'Marek Montážnik',
|
|
roleId: userRole.id,
|
|
},
|
|
}),
|
|
prisma.user.upsert({
|
|
where: { email: 'zakaznik@zakaznik.sk' },
|
|
update: {},
|
|
create: {
|
|
email: 'zakaznik@zakaznik.sk',
|
|
password: hashedZakaznik,
|
|
name: 'Zákazník Demo',
|
|
roleId: customerRole.id,
|
|
},
|
|
}),
|
|
]);
|
|
console.log(' Created demo users: root@root.sk, admin@admin.sk, user1-3@user.sk, zakaznik@zakaznik.sk');
|
|
|
|
// All service records are assigned to root user
|
|
const defaultUserId = rootUser.id;
|
|
|
|
// ===== CUSTOMERS FROM OLD DB =====
|
|
console.log('Creating customers from old DB...');
|
|
const customerMap: Record<number, string> = {};
|
|
|
|
for (const c of CUSTOMERS) {
|
|
const customer = await prisma.customer.upsert({
|
|
where: { externalId: String(c.oldId) },
|
|
update: {},
|
|
create: {
|
|
name: c.name,
|
|
ico: c.ico && c.ico !== '0' ? c.ico : undefined,
|
|
dic: c.dic && c.dic !== '0' ? c.dic : undefined,
|
|
address: c.address,
|
|
email: c.email,
|
|
contactPerson: c.contactPerson,
|
|
contactPhone: c.phone,
|
|
externalId: String(c.oldId),
|
|
externalSource: 'old-helpdesk',
|
|
createdById: rootUser.id,
|
|
},
|
|
});
|
|
customerMap[c.oldId] = customer.id;
|
|
}
|
|
console.log(` Created ${Object.keys(customerMap).length} customers`);
|
|
|
|
// ===== ANALYZE SERVICE RECORDS =====
|
|
// Zistiť pre každé zariadenie: aké typy revízií má a kedy bola východzia revízia
|
|
console.log('Analyzing service records for revision metadata...');
|
|
|
|
interface EquipMeta {
|
|
typeCodes: Set<string>;
|
|
vychodziaDate: string | null;
|
|
}
|
|
|
|
const equipMeta: Map<number, EquipMeta> = new Map();
|
|
|
|
for (const [auditId, date, , , note] of SERVICE_RECORDS) {
|
|
const audit = AUDIT_MAP[auditId];
|
|
if (!audit) continue;
|
|
|
|
const hwId = audit.equipmentHwId;
|
|
if (!equipMeta.has(hwId)) {
|
|
equipMeta.set(hwId, { typeCodes: new Set(), vychodziaDate: null });
|
|
}
|
|
|
|
const meta = equipMeta.get(hwId)!;
|
|
|
|
// Klasifikovať typ revízie z poznámky
|
|
const typeCode = classifyNote(note, audit.periodicity);
|
|
meta.typeCodes.add(typeCode);
|
|
|
|
// Detekovať dátum "Východzej revízie"
|
|
if (isVychodziaNote(note) && !meta.vychodziaDate) {
|
|
meta.vychodziaDate = date;
|
|
}
|
|
}
|
|
|
|
console.log(` Found revision metadata for ${equipMeta.size} equipment items`);
|
|
for (const [hwId, meta] of equipMeta) {
|
|
if (meta.vychodziaDate) {
|
|
console.log(` hwId ${hwId}: východzia=${meta.vychodziaDate}, types=[${[...meta.typeCodes].join(', ')}]`);
|
|
}
|
|
}
|
|
|
|
// ===== EQUIPMENT FROM OLD DB =====
|
|
console.log('Creating equipment from old DB...');
|
|
|
|
// Fetch equipment type IDs
|
|
const equipTypes = await prisma.equipmentType.findMany();
|
|
const equipTypeMap: Record<string, string> = {};
|
|
for (const t of equipTypes) {
|
|
equipTypeMap[t.code] = t.id;
|
|
}
|
|
|
|
const equipmentIdMap: Record<number, string> = {}; // hwId -> new ID
|
|
|
|
for (const e of EQUIPMENT) {
|
|
const typeId = equipTypeMap[e.typeCode] || equipTypeMap['OTHER'];
|
|
const customerId = customerMap[e.companyId] || undefined;
|
|
const meta = equipMeta.get(e.hwId);
|
|
|
|
const equip = await prisma.equipment.create({
|
|
data: {
|
|
name: e.name,
|
|
typeId,
|
|
brand: e.brand || undefined,
|
|
customerId,
|
|
address: e.location || '-',
|
|
location: e.location || undefined,
|
|
partNumber: e.partNumber && !['-', ' ', '', '0'].includes(e.partNumber.trim()) ? e.partNumber : undefined,
|
|
serialNumber: e.serialNumber && !['-', ' ', '', '0', '.', '..', '...', ':', ',', '.,', ',.', ',,', '.-', '---', '-----', '-,'].includes(e.serialNumber.trim()) ? e.serialNumber : undefined,
|
|
description: e.description && !['-', ' ', '', 'ok'].includes(e.description.trim()) ? e.description : undefined,
|
|
installDate: e.installDate ? new Date(e.installDate) : undefined,
|
|
revisionCycleStart: meta?.vychodziaDate ? new Date(meta.vychodziaDate) : undefined,
|
|
createdById: rootUser.id,
|
|
},
|
|
});
|
|
equipmentIdMap[e.hwId] = equip.id;
|
|
}
|
|
console.log(` Created ${Object.keys(equipmentIdMap).length} equipment items`);
|
|
|
|
// ===== EQUIPMENT REVISION SCHEDULES =====
|
|
// Pre každé zariadenie vytvoriť záznamy o tom, aké typy revízií má priradené
|
|
console.log('Creating equipment revision schedules...');
|
|
|
|
const revisionTypes = await prisma.revisionType.findMany();
|
|
const revTypeMap: Record<string, string> = {};
|
|
for (const t of revisionTypes) {
|
|
revTypeMap[t.code] = t.id;
|
|
}
|
|
|
|
let scheduleCount = 0;
|
|
for (const [hwId, meta] of equipMeta) {
|
|
const equipId = equipmentIdMap[hwId];
|
|
if (!equipId) continue;
|
|
|
|
for (const typeCode of meta.typeCodes) {
|
|
const revTypeId = revTypeMap[typeCode];
|
|
if (!revTypeId) continue;
|
|
|
|
await prisma.equipmentRevisionSchedule.create({
|
|
data: {
|
|
equipmentId: equipId,
|
|
revisionTypeId: revTypeId,
|
|
},
|
|
});
|
|
scheduleCount++;
|
|
}
|
|
}
|
|
console.log(` Created ${scheduleCount} equipment revision schedules`);
|
|
|
|
// ===== REVISION RECORDS FROM OLD DB =====
|
|
console.log('Creating revision records from old DB...');
|
|
|
|
let createdRevisions = 0;
|
|
let skippedRevisions = 0;
|
|
|
|
// Deduplicate: same equipment + type + date = duplicate in old data
|
|
const seen = new Set<string>();
|
|
const uniqueRevisions = SERVICE_RECORDS
|
|
.map(([auditId, date, nextDate, _userEmail, note]) => {
|
|
const audit = AUDIT_MAP[auditId];
|
|
if (!audit) return null;
|
|
|
|
const equipId = equipmentIdMap[audit.equipmentHwId];
|
|
if (!equipId) return null;
|
|
|
|
// Klasifikovať typ revízie podľa poznámky (nie len podľa audit periodicity)
|
|
const typeCode = classifyNote(note, audit.periodicity);
|
|
const typeId = revTypeMap[typeCode];
|
|
if (!typeId) return null;
|
|
|
|
// Deduplicate by equipment + type + date
|
|
const key = `${equipId}-${typeId}-${date}`;
|
|
if (seen.has(key)) return null;
|
|
seen.add(key);
|
|
|
|
return {
|
|
equipmentId: equipId,
|
|
typeId,
|
|
performedDate: new Date(date),
|
|
nextDueDate: nextDate ? new Date(nextDate) : null,
|
|
performedById: defaultUserId,
|
|
notes: note && note.trim() ? note.trim() : undefined,
|
|
result: 'OK',
|
|
};
|
|
})
|
|
.filter((r): r is NonNullable<typeof r> => r !== null);
|
|
|
|
// Process in batches
|
|
const BATCH_SIZE = 50;
|
|
for (let i = 0; i < uniqueRevisions.length; i += BATCH_SIZE) {
|
|
const batch = uniqueRevisions.slice(i, i + BATCH_SIZE);
|
|
await prisma.revision.createMany({ data: batch });
|
|
createdRevisions += batch.length;
|
|
}
|
|
skippedRevisions = SERVICE_RECORDS.length - uniqueRevisions.length;
|
|
console.log(` Created ${createdRevisions} revisions (${skippedRevisions} duplicates skipped)`);
|
|
|
|
// ===== DEMO TASKS =====
|
|
console.log('Creating demo tasks...');
|
|
|
|
const statuses = await prisma.taskStatus.findMany();
|
|
const priorities = await prisma.priority.findMany();
|
|
|
|
const statusByCode = (code: string) => statuses.find(s => s.code === code)!.id;
|
|
const priorityByCode = (code: string) => priorities.find(p => p.code === code)!.id;
|
|
|
|
const demoTasks = [
|
|
// --- Admin zadáva úlohy pre userov ---
|
|
{
|
|
title: 'Objednať UTP káble Cat6a (100m)',
|
|
description: 'Objednať 3 balenia UTP káblov Cat6a po 100m pre nový serverový rack. Dodávateľ: Senetic alebo TS Bohemia.',
|
|
statusCode: 'NEW',
|
|
priorityCode: 'MEDIUM',
|
|
createdById: demoAdmin.id,
|
|
assignees: [user1.id],
|
|
deadline: new Date('2026-03-10'),
|
|
},
|
|
{
|
|
title: 'Výmena UPS batérií v racku R3',
|
|
description: 'APC Smart-UPS 1500VA - batérie sú po záruke, hlási "Replace Battery". Objednať RBC7 a vymeniť.',
|
|
statusCode: 'NEW',
|
|
priorityCode: 'URGENT',
|
|
createdById: demoAdmin.id,
|
|
assignees: [user2.id],
|
|
deadline: new Date('2026-02-28'),
|
|
},
|
|
{
|
|
title: 'Inštalácia kamerového systému - vstupná hala',
|
|
description: 'Namontovať 2x Hikvision DS-2CD2143G2 + nastaviť NVR záznam na 30 dní. Kabeláž už je pripravená.',
|
|
statusCode: 'IN_PROGRESS',
|
|
priorityCode: 'MEDIUM',
|
|
createdById: demoAdmin.id,
|
|
assignees: [user3.id],
|
|
deadline: new Date('2026-03-15'),
|
|
},
|
|
{
|
|
title: 'Oprava Wi-Fi pokrytia v zasadačke B2',
|
|
description: 'Zákazník hlási slabý signál. Skontrolovať site survey, prípadne pridať ďalší AP alebo presunúť existujúci.',
|
|
statusCode: 'NEW',
|
|
priorityCode: 'HIGH',
|
|
createdById: demoAdmin.id,
|
|
assignees: [user1.id, user2.id],
|
|
deadline: new Date('2026-03-08'),
|
|
},
|
|
{
|
|
title: 'Objednať toner do tlačiarne HP LaserJet Pro',
|
|
description: 'Kancelária 3. poschodie - toner CF259X je takmer prázdny. Objednať 2 kusy na sklad.',
|
|
statusCode: 'COMPLETED',
|
|
priorityCode: 'LOW',
|
|
createdById: demoAdmin.id,
|
|
assignees: [user1.id],
|
|
},
|
|
// --- Root zadáva úlohy pre userov a admina ---
|
|
{
|
|
title: 'Nastaviť switch Cisco SG350 v serverovni',
|
|
description: 'Konfigurácia VLAN 10, 20, 30. Trunk port na uplink. Nastaviť SNMP monitoring a port security na access portoch.',
|
|
statusCode: 'IN_PROGRESS',
|
|
priorityCode: 'HIGH',
|
|
createdById: demoRoot.id,
|
|
assignees: [demoAdmin.id, user1.id],
|
|
deadline: new Date('2026-03-05'),
|
|
},
|
|
{
|
|
title: 'Nastaviť VPN tunel medzi pobočkami',
|
|
description: 'IPSec site-to-site VPN medzi Bratislava a Košice. MikroTik RB4011 na oboch stranách. Zdieľaný subnet 10.10.0.0/24.',
|
|
statusCode: 'IN_PROGRESS',
|
|
priorityCode: 'HIGH',
|
|
createdById: demoRoot.id,
|
|
assignees: [user2.id, user3.id],
|
|
deadline: new Date('2026-03-01'),
|
|
},
|
|
{
|
|
title: 'Migrácia mailboxov na Microsoft 365',
|
|
description: 'Presunúť 25 mailboxov z on-prem Exchange na M365. Pripraviť migračný plán, otestovať na 3 pilotných užívateľoch.',
|
|
statusCode: 'NEW',
|
|
priorityCode: 'MEDIUM',
|
|
createdById: demoRoot.id,
|
|
assignees: [demoAdmin.id, user1.id, user2.id],
|
|
deadline: new Date('2026-04-01'),
|
|
},
|
|
// --- Úlohy kde admin robí sám ---
|
|
{
|
|
title: 'Zálohovanie konfigurácie sieťových zariadení',
|
|
description: 'Exportovať running-config zo všetkých switchov a routerov. Uložiť do Git repozitára na NAS.',
|
|
statusCode: 'COMPLETED',
|
|
priorityCode: 'MEDIUM',
|
|
createdById: demoAdmin.id,
|
|
assignees: [demoAdmin.id],
|
|
},
|
|
{
|
|
title: 'Aktualizovať firmware na AP Ubiquiti U6-Pro',
|
|
description: 'Nová verzia firmware opravuje bug s roamingom klientov. Aktualizovať všetkých 8 AP cez UniFi Controller.',
|
|
statusCode: 'REVIEW',
|
|
priorityCode: 'LOW',
|
|
createdById: demoAdmin.id,
|
|
assignees: [demoAdmin.id, user3.id],
|
|
},
|
|
// --- Root + admin spoločne ---
|
|
{
|
|
title: 'Audit sieťovej infraštruktúry',
|
|
description: 'Kompletný audit všetkých aktívnych prvkov, kontrola firmware verzií, kontrola prístupových práv na zariadeniach.',
|
|
statusCode: 'NEW',
|
|
priorityCode: 'MEDIUM',
|
|
createdById: demoRoot.id,
|
|
assignees: [demoAdmin.id],
|
|
deadline: new Date('2026-03-20'),
|
|
},
|
|
// --- User si vytvára vlastnú úlohu ---
|
|
{
|
|
title: 'Zdokumentovať zapojenie patch panelov v racku R1',
|
|
description: 'Vytvoriť schému zapojenia patch panelov a označiť káble podľa štandardu TIA-606.',
|
|
statusCode: 'IN_PROGRESS',
|
|
priorityCode: 'LOW',
|
|
createdById: user2.id,
|
|
assignees: [user2.id],
|
|
},
|
|
];
|
|
|
|
for (const t of demoTasks) {
|
|
const task = await prisma.task.create({
|
|
data: {
|
|
title: t.title,
|
|
description: t.description,
|
|
statusId: statusByCode(t.statusCode),
|
|
priorityId: priorityByCode(t.priorityCode),
|
|
createdById: t.createdById,
|
|
deadline: t.deadline,
|
|
completedAt: t.statusCode === 'COMPLETED' ? new Date() : undefined,
|
|
},
|
|
});
|
|
|
|
if (t.assignees.length > 0) {
|
|
await prisma.taskAssignee.createMany({
|
|
data: t.assignees.map(userId => ({
|
|
taskId: task.id,
|
|
userId,
|
|
})),
|
|
});
|
|
}
|
|
}
|
|
console.log(` Created ${demoTasks.length} demo tasks`);
|
|
|
|
console.log('Seeding completed!');
|
|
}
|
|
|
|
seed()
|
|
.catch((error) => {
|
|
console.error('Seeding failed:', error);
|
|
process.exit(1);
|
|
})
|
|
.finally(async () => {
|
|
await prisma.$disconnect();
|
|
});
|