Revízny systém - kompletná implementácia
- 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>
This commit is contained in:
@@ -1,8 +1,44 @@
|
||||
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...');
|
||||
|
||||
@@ -88,10 +124,17 @@ async function seed() {
|
||||
skipDuplicates: true,
|
||||
data: [
|
||||
{ code: 'EPS', name: 'Elektrická požiarna signalizácia', color: '#3B82F6', order: 1 },
|
||||
{ code: 'HSP', name: 'Hasiaci systém', color: '#EF4444', order: 2 },
|
||||
{ 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,
|
||||
})),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -100,10 +143,15 @@ async function seed() {
|
||||
await prisma.revisionType.createMany({
|
||||
skipDuplicates: true,
|
||||
data: [
|
||||
{ code: 'QUARTERLY', name: 'Štvrťročná revízia', intervalDays: 90, reminderDays: 14, color: '#FFA500', order: 1 },
|
||||
{ code: 'BIANNUAL', name: 'Polročná revízia', intervalDays: 180, reminderDays: 21, color: '#FBBF24', order: 2 },
|
||||
{ code: 'ANNUAL', name: 'Ročná revízia', intervalDays: 365, reminderDays: 30, color: '#DC2626', order: 3 },
|
||||
{ code: 'EMERGENCY', name: 'Mimoriadna revízia', intervalDays: 0, reminderDays: 0, color: '#DC2626', order: 4 },
|
||||
{ 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 },
|
||||
],
|
||||
});
|
||||
|
||||
@@ -209,56 +257,451 @@ async function seed() {
|
||||
{ label: '30 minút', minutes: 30 },
|
||||
{ label: '1 hodina', minutes: 60 },
|
||||
{ label: '3 hodiny', minutes: 180 },
|
||||
{ label: 'Zajtra ráno', type: 'tomorrow', hour: 9 },
|
||||
{ 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',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// ===== DEMO USERS =====
|
||||
console.log('Creating demo users...');
|
||||
// ===== 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) {
|
||||
await prisma.user.upsert({
|
||||
where: { email: 'root@helpdesk.sk' },
|
||||
update: { password: await bcrypt.hash('root123', 10) },
|
||||
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@helpdesk.sk',
|
||||
password: await bcrypt.hash('root123', 10),
|
||||
name: 'Root Admin',
|
||||
email: 'root@root.sk',
|
||||
password: hashedRoot,
|
||||
name: 'Root Používateľ',
|
||||
roleId: rootRole.id,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.user.upsert({
|
||||
where: { email: 'admin@helpdesk.sk' },
|
||||
update: { password: await bcrypt.hash('admin123', 10) },
|
||||
}),
|
||||
prisma.user.upsert({
|
||||
where: { email: 'admin@admin.sk' },
|
||||
update: {},
|
||||
create: {
|
||||
email: 'admin@helpdesk.sk',
|
||||
password: await bcrypt.hash('admin123', 10),
|
||||
name: 'Peter Admin',
|
||||
email: 'admin@admin.sk',
|
||||
password: hashedAdmin,
|
||||
name: 'Admin Používateľ',
|
||||
roleId: adminRole.id,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.user.upsert({
|
||||
where: { email: 'user@helpdesk.sk' },
|
||||
update: { password: await bcrypt.hash('user123', 10) },
|
||||
}),
|
||||
prisma.user.upsert({
|
||||
where: { email: 'user1@user.sk' },
|
||||
update: {},
|
||||
create: {
|
||||
email: 'user@helpdesk.sk',
|
||||
password: await bcrypt.hash('user123', 10),
|
||||
name: 'Martin Používateľ',
|
||||
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!');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user