Initial commit: Helpdesk application setup

- Backend: Node.js/TypeScript with Prisma ORM
- Frontend: Vite + TypeScript
- Project configuration and documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 08:53:22 +01:00
commit e4f63a135e
103 changed files with 19913 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
import { Request, Response } from 'express';
import { authService } from '../services/auth.service';
import { successResponse, errorResponse } from '../utils/helpers';
import { AuthRequest } from '../middleware/auth.middleware';
import { logActivity } from '../middleware/activityLog.middleware';
export const register = async (req: Request, res: Response): Promise<void> => {
try {
const user = await authService.register(req.body);
await logActivity(
user.id,
'CREATE',
'User',
user.id,
{ email: user.email, name: user.name },
req.ip,
req.get('User-Agent')
);
successResponse(res, {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
}, 'Registrácia úspešná.', 201);
} catch (error) {
if (error instanceof Error) {
errorResponse(res, error.message, 400);
} else {
errorResponse(res, 'Chyba pri registrácii.', 500);
}
}
};
export const login = async (req: Request, res: Response): Promise<void> => {
try {
const { user, tokens } = await authService.login(req.body);
await logActivity(
user.id,
'LOGIN',
'User',
user.id,
{ email: user.email },
req.ip,
req.get('User-Agent')
);
successResponse(res, {
user: {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
},
...tokens,
}, 'Prihlásenie úspešné.');
} catch (error) {
if (error instanceof Error) {
errorResponse(res, error.message, 401);
} else {
errorResponse(res, 'Chyba pri prihlásení.', 500);
}
}
};
export const refresh = async (req: Request, res: Response): Promise<void> => {
try {
const { refreshToken } = req.body;
if (!refreshToken) {
errorResponse(res, 'Refresh token je povinný.', 400);
return;
}
const tokens = await authService.refreshTokens(refreshToken);
successResponse(res, tokens, 'Tokeny obnovené.');
} catch (error) {
if (error instanceof Error) {
errorResponse(res, error.message, 401);
} else {
errorResponse(res, 'Chyba pri obnove tokenov.', 500);
}
}
};
export const logout = async (req: AuthRequest, res: Response): Promise<void> => {
try {
if (req.user) {
await logActivity(
req.user.userId,
'LOGOUT',
'User',
req.user.userId,
{ email: req.user.email },
req.ip,
req.get('User-Agent')
);
}
successResponse(res, null, 'Odhlásenie úspešné.');
} catch {
errorResponse(res, 'Chyba pri odhlásení.', 500);
}
};
export const getMe = async (req: AuthRequest, res: Response): Promise<void> => {
try {
if (!req.user) {
errorResponse(res, 'Nie ste prihlásený.', 401);
return;
}
const user = await authService.getMe(req.user.userId);
successResponse(res, {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
});
} catch (error) {
if (error instanceof Error) {
errorResponse(res, error.message, 404);
} else {
errorResponse(res, 'Chyba pri získaní údajov.', 500);
}
}
};

View File

@@ -0,0 +1,216 @@
import { Response } from 'express';
import prisma from '../config/database';
import { successResponse, errorResponse, paginatedResponse, parseQueryInt, getParam, getQueryString } from '../utils/helpers';
import { AuthRequest } from '../middleware/auth.middleware';
export const getCustomers = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const page = parseQueryInt(req.query.page, 1);
const limit = parseQueryInt(req.query.limit, 20);
const skip = (page - 1) * limit;
const search = getQueryString(req, 'search');
const active = getQueryString(req, 'active');
const where = {
...(active !== undefined && { active: active === 'true' }),
...(search && {
OR: [
{ name: { contains: search, mode: 'insensitive' as const } },
{ email: { contains: search, mode: 'insensitive' as const } },
{ ico: { contains: search, mode: 'insensitive' as const } },
],
}),
};
const [customers, total] = await Promise.all([
prisma.customer.findMany({
where,
skip,
take: limit,
orderBy: { name: 'asc' },
include: {
_count: {
select: {
projects: true,
equipment: true,
rmas: true,
},
},
},
}),
prisma.customer.count({ where }),
]);
paginatedResponse(res, customers, total, page, limit);
} catch (error) {
console.error('Error fetching customers:', error);
errorResponse(res, 'Chyba pri načítaní zákazníkov.', 500);
}
};
export const getCustomer = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const customer = await prisma.customer.findUnique({
where: { id },
include: {
createdBy: {
select: { id: true, name: true, email: true },
},
_count: {
select: {
projects: true,
equipment: true,
rmas: true,
},
},
},
});
if (!customer) {
errorResponse(res, 'Zákazník nebol nájdený.', 404);
return;
}
successResponse(res, customer);
} catch (error) {
console.error('Error fetching customer:', error);
errorResponse(res, 'Chyba pri načítaní zákazníka.', 500);
}
};
export const createCustomer = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const customer = await prisma.customer.create({
data: {
...req.body,
createdById: req.user!.userId,
},
});
if (req.logActivity) {
await req.logActivity('CREATE', 'Customer', customer.id, { name: customer.name });
}
successResponse(res, customer, 'Zákazník bol vytvorený.', 201);
} catch (error) {
console.error('Error creating customer:', error);
errorResponse(res, 'Chyba pri vytváraní zákazníka.', 500);
}
};
export const updateCustomer = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const existing = await prisma.customer.findUnique({ where: { id } });
if (!existing) {
errorResponse(res, 'Zákazník nebol nájdený.', 404);
return;
}
const customer = await prisma.customer.update({
where: { id },
data: req.body,
});
if (req.logActivity) {
await req.logActivity('UPDATE', 'Customer', id, req.body);
}
successResponse(res, customer, 'Zákazník bol aktualizovaný.');
} catch (error) {
console.error('Error updating customer:', error);
errorResponse(res, 'Chyba pri aktualizácii zákazníka.', 500);
}
};
export const deleteCustomer = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const customer = await prisma.customer.findUnique({ where: { id } });
if (!customer) {
errorResponse(res, 'Zákazník nebol nájdený.', 404);
return;
}
// Soft delete
await prisma.customer.update({
where: { id },
data: { active: false },
});
if (req.logActivity) {
await req.logActivity('DELETE', 'Customer', id);
}
successResponse(res, null, 'Zákazník bol deaktivovaný.');
} catch (error) {
console.error('Error deleting customer:', error);
errorResponse(res, 'Chyba pri mazaní zákazníka.', 500);
}
};
export const getCustomerProjects = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const projects = await prisma.project.findMany({
where: { customerId: id },
include: {
status: true,
owner: { select: { id: true, name: true } },
_count: { select: { tasks: true } },
},
orderBy: { createdAt: 'desc' },
});
successResponse(res, projects);
} catch (error) {
console.error('Error fetching customer projects:', error);
errorResponse(res, 'Chyba pri načítaní projektov.', 500);
}
};
export const getCustomerEquipment = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const equipment = await prisma.equipment.findMany({
where: { customerId: id, active: true },
include: {
type: true,
},
orderBy: { createdAt: 'desc' },
});
successResponse(res, equipment);
} catch (error) {
console.error('Error fetching customer equipment:', error);
errorResponse(res, 'Chyba pri načítaní zariadení.', 500);
}
};
export const getCustomerRMAs = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const rmas = await prisma.rMA.findMany({
where: { customerId: id },
include: {
status: true,
proposedSolution: true,
},
orderBy: { createdAt: 'desc' },
});
successResponse(res, rmas);
} catch (error) {
console.error('Error fetching customer RMAs:', error);
errorResponse(res, 'Chyba pri načítaní reklamácií.', 500);
}
};

View File

@@ -0,0 +1,333 @@
import { Response } from 'express';
import prisma from '../config/database';
import { successResponse, errorResponse } from '../utils/helpers';
import { AuthRequest } from '../middleware/auth.middleware';
// Hlavný dashboard endpoint - štatistiky pre karty
export const getDashboard = async (_req: AuthRequest, res: Response): Promise<void> => {
try {
const today = new Date();
const nextMonth = new Date(today);
nextMonth.setDate(nextMonth.getDate() + 30);
const [
totalProjects,
activeProjects,
totalTasks,
pendingTasks,
inProgressTasks,
totalCustomers,
activeCustomers,
totalEquipment,
upcomingRevisions,
totalRMAs,
pendingRMAs,
] = await Promise.all([
prisma.project.count(),
prisma.project.count({ where: { status: { isFinal: false } } }),
prisma.task.count(),
prisma.task.count({ where: { status: { code: 'NEW' } } }),
prisma.task.count({ where: { status: { code: 'IN_PROGRESS' } } }),
prisma.customer.count(),
prisma.customer.count({ where: { active: true } }),
prisma.equipment.count({ where: { active: true } }),
prisma.revision.count({
where: {
nextDueDate: {
gte: today,
lte: nextMonth,
},
},
}),
prisma.rMA.count(),
prisma.rMA.count({ where: { status: { isFinal: false } } }),
]);
successResponse(res, {
projects: {
total: totalProjects,
active: activeProjects,
},
tasks: {
total: totalTasks,
pending: pendingTasks,
inProgress: inProgressTasks,
},
customers: {
total: totalCustomers,
active: activeCustomers,
},
equipment: {
total: totalEquipment,
upcomingRevisions,
},
rma: {
total: totalRMAs,
pending: pendingRMAs,
},
});
} catch (error) {
console.error('Error fetching dashboard:', error);
errorResponse(res, 'Chyba pri načítaní dashboardu.', 500);
}
};
export const getDashboardToday = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
const userId = req.user!.userId;
const [myTasks, myProjects, recentRMAs] = await Promise.all([
// Tasks assigned to me that are not completed
prisma.task.findMany({
where: {
assignees: { some: { userId } },
status: { isFinal: false },
},
include: {
status: true,
priority: true,
project: { select: { id: true, name: true } },
createdBy: { select: { id: true, name: true } },
assignees: { include: { user: { select: { id: true, name: true } } } },
},
orderBy: [{ priority: { level: 'desc' } }, { deadline: 'asc' }],
take: 10,
}),
// My active projects
prisma.project.findMany({
where: {
OR: [
{ ownerId: userId },
{ members: { some: { userId } } },
],
status: { isFinal: false },
},
include: {
status: true,
_count: { select: { tasks: true } },
},
take: 5,
}),
// Recent RMAs
prisma.rMA.findMany({
where: {
OR: [
{ assignedToId: userId },
{ createdById: userId },
],
},
include: {
status: true,
},
orderBy: { createdAt: 'desc' },
take: 5,
}),
]);
successResponse(res, {
myTasks,
myProjects,
recentRMAs,
});
} catch (error) {
console.error('Error fetching dashboard today:', error);
errorResponse(res, 'Chyba pri načítaní dashboardu.', 500);
}
};
export const getDashboardWeek = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const today = new Date();
today.setHours(0, 0, 0, 0);
const nextWeek = new Date(today);
nextWeek.setDate(nextWeek.getDate() + 7);
const userId = req.user!.userId;
const [tasksDeadlineThisWeek, upcomingRevisions] = await Promise.all([
// Tasks with deadline this week
prisma.task.findMany({
where: {
deadline: {
gte: today,
lte: nextWeek,
},
status: { isFinal: false },
OR: [
{ createdById: userId },
{ assignees: { some: { userId } } },
],
},
include: {
status: true,
priority: true,
project: { select: { id: true, name: true } },
assignees: { include: { user: { select: { id: true, name: true } } } },
},
orderBy: { deadline: 'asc' },
}),
// Upcoming equipment revisions
prisma.revision.findMany({
where: {
nextDueDate: {
gte: today,
lte: nextWeek,
},
},
include: {
equipment: {
include: {
type: true,
customer: { select: { id: true, name: true } },
},
},
type: true,
},
orderBy: { nextDueDate: 'asc' },
}),
]);
successResponse(res, {
tasksDeadlineThisWeek,
upcomingRevisions,
});
} catch (error) {
console.error('Error fetching dashboard week:', error);
errorResponse(res, 'Chyba pri načítaní týždenného prehľadu.', 500);
}
};
export const getDashboardStats = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const [
totalProjects,
activeProjects,
totalTasks,
completedTasks,
totalCustomers,
totalEquipment,
totalRMAs,
openRMAs,
] = await Promise.all([
prisma.project.count(),
prisma.project.count({ where: { status: { isFinal: false } } }),
prisma.task.count(),
prisma.task.count({ where: { status: { isFinal: true } } }),
prisma.customer.count({ where: { active: true } }),
prisma.equipment.count({ where: { active: true } }),
prisma.rMA.count(),
prisma.rMA.count({ where: { status: { isFinal: false } } }),
]);
successResponse(res, {
projects: {
total: totalProjects,
active: activeProjects,
},
tasks: {
total: totalTasks,
completed: completedTasks,
completionRate: totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0,
},
customers: {
total: totalCustomers,
},
equipment: {
total: totalEquipment,
},
rmas: {
total: totalRMAs,
open: openRMAs,
},
});
} catch (error) {
console.error('Error fetching dashboard stats:', error);
errorResponse(res, 'Chyba pri načítaní štatistík.', 500);
}
};
export const getDashboardReminders = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const today = new Date();
const nextMonth = new Date(today);
nextMonth.setDate(nextMonth.getDate() + 30);
const userId = req.user!.userId;
const [taskReminders, equipmentRevisions, overdueRMAs] = await Promise.all([
// Task reminders
prisma.reminder.findMany({
where: {
userId,
dismissed: false,
remindAt: {
lte: nextMonth,
},
},
include: {
task: {
include: {
status: true,
project: { select: { id: true, name: true } },
},
},
},
orderBy: { remindAt: 'asc' },
}),
// Equipment revision reminders
prisma.revision.findMany({
where: {
nextDueDate: {
gte: today,
lte: nextMonth,
},
},
include: {
equipment: {
include: {
type: true,
customer: { select: { id: true, name: true } },
},
},
type: true,
},
orderBy: { nextDueDate: 'asc' },
take: 10,
}),
// Overdue or old RMAs
prisma.rMA.findMany({
where: {
status: { isFinal: false },
createdAt: {
lte: new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000), // Older than 7 days
},
},
include: {
status: true,
customer: { select: { id: true, name: true } },
},
orderBy: { createdAt: 'asc' },
take: 10,
}),
]);
successResponse(res, {
taskReminders,
equipmentRevisions,
overdueRMAs,
});
} catch (error) {
console.error('Error fetching dashboard reminders:', error);
errorResponse(res, 'Chyba pri načítaní upomienok.', 500);
}
};

View File

@@ -0,0 +1,313 @@
import { Response } from 'express';
import prisma from '../config/database';
import { successResponse, errorResponse, paginatedResponse, parseQueryInt, getParam, getQueryString } from '../utils/helpers';
import { AuthRequest } from '../middleware/auth.middleware';
export const getEquipment = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const page = parseQueryInt(req.query.page, 1);
const limit = parseQueryInt(req.query.limit, 20);
const skip = (page - 1) * limit;
const search = getQueryString(req, 'search');
const active = getQueryString(req, 'active');
const typeId = getQueryString(req, 'typeId');
const customerId = getQueryString(req, 'customerId');
const where = {
...(active !== undefined && { active: active === 'true' }),
...(typeId && { typeId }),
...(customerId && { customerId }),
...(search && {
OR: [
{ name: { contains: search, mode: 'insensitive' as const } },
{ serialNumber: { contains: search, mode: 'insensitive' as const } },
{ address: { contains: search, mode: 'insensitive' as const } },
],
}),
};
const [equipment, total] = await Promise.all([
prisma.equipment.findMany({
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
include: {
type: true,
customer: { select: { id: true, name: true } },
createdBy: { select: { id: true, name: true } },
_count: { select: { revisions: true } },
},
}),
prisma.equipment.count({ where }),
]);
paginatedResponse(res, equipment, total, page, limit);
} catch (error) {
console.error('Error fetching equipment:', error);
errorResponse(res, 'Chyba pri načítaní zariadení.', 500);
}
};
export const getEquipmentById = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const equipment = await prisma.equipment.findUnique({
where: { id },
include: {
type: true,
customer: true,
createdBy: { select: { id: true, name: true, email: true } },
revisions: {
orderBy: { performedDate: 'desc' },
take: 5,
include: {
type: true,
performedBy: { select: { id: true, name: true } },
},
},
attachments: {
orderBy: { uploadedAt: 'desc' },
},
tags: { include: { tag: true } },
},
});
if (!equipment) {
errorResponse(res, 'Zariadenie nebolo nájdené.', 404);
return;
}
successResponse(res, equipment);
} catch (error) {
console.error('Error fetching equipment:', error);
errorResponse(res, 'Chyba pri načítaní zariadenia.', 500);
}
};
export const createEquipment = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const equipment = await prisma.equipment.create({
data: {
name: req.body.name,
typeId: req.body.typeId,
brand: req.body.brand,
model: req.body.model,
customerId: req.body.customerId || null,
address: req.body.address,
location: req.body.location,
partNumber: req.body.partNumber,
serialNumber: req.body.serialNumber,
installDate: req.body.installDate ? new Date(req.body.installDate) : null,
warrantyEnd: req.body.warrantyEnd ? new Date(req.body.warrantyEnd) : null,
warrantyStatus: req.body.warrantyStatus,
description: req.body.description,
notes: req.body.notes,
createdById: req.user!.userId,
},
include: {
type: true,
customer: { select: { id: true, name: true } },
},
});
if (req.logActivity) {
await req.logActivity('CREATE', 'Equipment', equipment.id, { name: equipment.name });
}
successResponse(res, equipment, 'Zariadenie bolo vytvorené.', 201);
} catch (error) {
console.error('Error creating equipment:', error);
errorResponse(res, 'Chyba pri vytváraní zariadenia.', 500);
}
};
export const updateEquipment = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const existing = await prisma.equipment.findUnique({ where: { id } });
if (!existing) {
errorResponse(res, 'Zariadenie nebolo nájdené.', 404);
return;
}
const updateData: Record<string, unknown> = {};
const fields = [
'name', 'typeId', 'brand', 'model', 'customerId', 'address',
'location', 'partNumber', 'serialNumber', 'warrantyStatus',
'description', 'notes', 'active'
];
for (const field of fields) {
if (req.body[field] !== undefined) {
updateData[field] = req.body[field];
}
}
if (req.body.installDate !== undefined) {
updateData.installDate = req.body.installDate ? new Date(req.body.installDate) : null;
}
if (req.body.warrantyEnd !== undefined) {
updateData.warrantyEnd = req.body.warrantyEnd ? new Date(req.body.warrantyEnd) : null;
}
const equipment = await prisma.equipment.update({
where: { id },
data: updateData,
include: {
type: true,
customer: { select: { id: true, name: true } },
},
});
if (req.logActivity) {
await req.logActivity('UPDATE', 'Equipment', id, updateData);
}
successResponse(res, equipment, 'Zariadenie bolo aktualizované.');
} catch (error) {
console.error('Error updating equipment:', error);
errorResponse(res, 'Chyba pri aktualizácii zariadenia.', 500);
}
};
export const deleteEquipment = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const equipment = await prisma.equipment.findUnique({ where: { id } });
if (!equipment) {
errorResponse(res, 'Zariadenie nebolo nájdené.', 404);
return;
}
// Soft delete
await prisma.equipment.update({
where: { id },
data: { active: false },
});
if (req.logActivity) {
await req.logActivity('DELETE', 'Equipment', id);
}
successResponse(res, null, 'Zariadenie bolo deaktivované.');
} catch (error) {
console.error('Error deleting equipment:', error);
errorResponse(res, 'Chyba pri mazaní zariadenia.', 500);
}
};
export const getEquipmentRevisions = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const revisions = await prisma.revision.findMany({
where: { equipmentId: id },
orderBy: { performedDate: 'desc' },
include: {
type: true,
performedBy: { select: { id: true, name: true } },
},
});
successResponse(res, revisions);
} catch (error) {
console.error('Error fetching equipment revisions:', error);
errorResponse(res, 'Chyba pri načítaní revízií.', 500);
}
};
export const createEquipmentRevision = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const equipment = await prisma.equipment.findUnique({ where: { id } });
if (!equipment) {
errorResponse(res, 'Zariadenie nebolo nájdené.', 404);
return;
}
const revisionType = await prisma.revisionType.findUnique({
where: { id: req.body.typeId },
});
if (!revisionType) {
errorResponse(res, 'Typ revízie nebol nájdený.', 404);
return;
}
const performedDate = new Date(req.body.performedDate);
const nextDueDate = new Date(performedDate);
nextDueDate.setDate(nextDueDate.getDate() + revisionType.intervalDays);
const reminderDate = new Date(nextDueDate);
reminderDate.setDate(reminderDate.getDate() - revisionType.reminderDays);
const revision = await prisma.revision.create({
data: {
equipmentId: id,
typeId: req.body.typeId,
performedDate,
nextDueDate,
reminderDate,
performedById: req.user!.userId,
findings: req.body.findings,
result: req.body.result,
notes: req.body.notes,
},
include: {
type: true,
performedBy: { select: { id: true, name: true } },
},
});
if (req.logActivity) {
await req.logActivity('CREATE', 'Revision', revision.id, {
equipmentId: id,
type: revisionType.name,
});
}
successResponse(res, revision, 'Revízia bola vytvorená.', 201);
} catch (error) {
console.error('Error creating revision:', error);
errorResponse(res, 'Chyba pri vytváraní revízie.', 500);
}
};
export const getEquipmentReminders = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const days = parseQueryInt(req.query.days, 30);
const futureDate = new Date();
futureDate.setDate(futureDate.getDate() + days);
const revisions = await prisma.revision.findMany({
where: {
nextDueDate: {
gte: new Date(),
lte: futureDate,
},
},
include: {
equipment: {
include: {
type: true,
customer: { select: { id: true, name: true } },
},
},
type: true,
},
orderBy: { nextDueDate: 'asc' },
});
successResponse(res, revisions);
} catch (error) {
console.error('Error fetching equipment reminders:', error);
errorResponse(res, 'Chyba pri načítaní upomienok.', 500);
}
};

View File

@@ -0,0 +1,288 @@
import { Response } from 'express';
import prisma from '../config/database';
import { successResponse, errorResponse, paginatedResponse, parseQueryInt, getParam, getQueryString } from '../utils/helpers';
import { AuthRequest } from '../middleware/auth.middleware';
import { configService } from '../services/config.service';
export const getProjects = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const page = parseQueryInt(req.query.page, 1);
const limit = parseQueryInt(req.query.limit, 20);
const skip = (page - 1) * limit;
const search = getQueryString(req, 'search');
const statusId = getQueryString(req, 'statusId');
const customerId = getQueryString(req, 'customerId');
const ownerId = getQueryString(req, 'ownerId');
const where = {
...(statusId && { statusId }),
...(customerId && { customerId }),
...(ownerId && { ownerId }),
...(search && {
OR: [
{ name: { contains: search, mode: 'insensitive' as const } },
{ description: { contains: search, mode: 'insensitive' as const } },
],
}),
};
const [projects, total] = await Promise.all([
prisma.project.findMany({
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
include: {
status: true,
customer: { select: { id: true, name: true } },
owner: { select: { id: true, name: true, email: true } },
_count: { select: { tasks: true, members: true } },
},
}),
prisma.project.count({ where }),
]);
paginatedResponse(res, projects, total, page, limit);
} catch (error) {
console.error('Error fetching projects:', error);
errorResponse(res, 'Chyba pri načítaní projektov.', 500);
}
};
export const getProject = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const project = await prisma.project.findUnique({
where: { id },
include: {
status: true,
customer: true,
owner: { select: { id: true, name: true, email: true } },
members: {
include: {
user: { select: { id: true, name: true, email: true } },
},
},
tags: { include: { tag: true } },
_count: { select: { tasks: true } },
},
});
if (!project) {
errorResponse(res, 'Projekt nebol nájdený.', 404);
return;
}
successResponse(res, project);
} catch (error) {
console.error('Error fetching project:', error);
errorResponse(res, 'Chyba pri načítaní projektu.', 500);
}
};
export const createProject = async (req: AuthRequest, res: Response): Promise<void> => {
try {
let { statusId } = req.body;
// Get initial status if not provided
if (!statusId) {
const initialStatus = await configService.getInitialTaskStatus();
if (initialStatus) {
statusId = initialStatus.id;
} else {
// Fallback: vezmi prvý aktívny status
const allStatuses = await configService.getTaskStatuses();
const statuses = allStatuses as { id: string }[];
if (statuses.length > 0) {
statusId = statuses[0].id;
} else {
errorResponse(res, 'Žiadny status nie je nakonfigurovaný. Spustite seed.', 500);
return;
}
}
}
const project = await prisma.project.create({
data: {
name: req.body.name,
description: req.body.description,
customerId: req.body.customerId || null,
ownerId: req.user!.userId,
statusId,
softDeadline: req.body.softDeadline ? new Date(req.body.softDeadline) : null,
hardDeadline: req.body.hardDeadline ? new Date(req.body.hardDeadline) : null,
},
include: {
status: true,
customer: { select: { id: true, name: true } },
owner: { select: { id: true, name: true } },
},
});
if (req.logActivity) {
await req.logActivity('CREATE', 'Project', project.id, { name: project.name });
}
successResponse(res, project, 'Projekt bol vytvorený.', 201);
} catch (error) {
console.error('Error creating project:', error);
errorResponse(res, 'Chyba pri vytváraní projektu.', 500);
}
};
export const updateProject = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const existing = await prisma.project.findUnique({ where: { id } });
if (!existing) {
errorResponse(res, 'Projekt nebol nájdený.', 404);
return;
}
const updateData: Record<string, unknown> = {};
if (req.body.name) updateData.name = req.body.name;
if (req.body.description !== undefined) updateData.description = req.body.description;
if (req.body.customerId !== undefined) updateData.customerId = req.body.customerId || null;
if (req.body.statusId) updateData.statusId = req.body.statusId;
if (req.body.softDeadline !== undefined) {
updateData.softDeadline = req.body.softDeadline ? new Date(req.body.softDeadline) : null;
}
if (req.body.hardDeadline !== undefined) {
updateData.hardDeadline = req.body.hardDeadline ? new Date(req.body.hardDeadline) : null;
}
const project = await prisma.project.update({
where: { id },
data: updateData,
include: {
status: true,
customer: { select: { id: true, name: true } },
owner: { select: { id: true, name: true } },
},
});
if (req.logActivity) {
await req.logActivity('UPDATE', 'Project', id, updateData);
}
successResponse(res, project, 'Projekt bol aktualizovaný.');
} catch (error) {
console.error('Error updating project:', error);
errorResponse(res, 'Chyba pri aktualizácii projektu.', 500);
}
};
export const deleteProject = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const project = await prisma.project.findUnique({ where: { id } });
if (!project) {
errorResponse(res, 'Projekt nebol nájdený.', 404);
return;
}
await prisma.project.delete({ where: { id } });
if (req.logActivity) {
await req.logActivity('DELETE', 'Project', id);
}
successResponse(res, null, 'Projekt bol vymazaný.');
} catch (error) {
console.error('Error deleting project:', error);
errorResponse(res, 'Chyba pri mazaní projektu.', 500);
}
};
export const updateProjectStatus = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const { statusId } = req.body;
const project = await prisma.project.update({
where: { id },
data: { statusId },
include: { status: true },
});
if (req.logActivity) {
await req.logActivity('STATUS_CHANGE', 'Project', id, { statusId });
}
successResponse(res, project, 'Status projektu bol zmenený.');
} catch (error) {
console.error('Error updating project status:', error);
errorResponse(res, 'Chyba pri zmene statusu.', 500);
}
};
export const getProjectTasks = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const tasks = await prisma.task.findMany({
where: { projectId: id },
include: {
status: true,
priority: true,
assignees: {
include: { user: { select: { id: true, name: true } } },
},
},
orderBy: [{ priority: { level: 'desc' } }, { createdAt: 'desc' }],
});
successResponse(res, tasks);
} catch (error) {
console.error('Error fetching project tasks:', error);
errorResponse(res, 'Chyba pri načítaní úloh.', 500);
}
};
export const addProjectMember = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const { userId } = req.body;
const member = await prisma.projectMember.create({
data: {
projectId: id,
userId,
},
include: {
user: { select: { id: true, name: true, email: true } },
},
});
successResponse(res, member, 'Člen bol pridaný.', 201);
} catch (error) {
console.error('Error adding project member:', error);
errorResponse(res, 'Chyba pri pridávaní člena.', 500);
}
};
export const removeProjectMember = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const userId = getParam(req, 'userId');
await prisma.projectMember.delete({
where: {
projectId_userId: {
projectId: id,
userId,
},
},
});
successResponse(res, null, 'Člen bol odstránený.');
} catch (error) {
console.error('Error removing project member:', error);
errorResponse(res, 'Chyba pri odstraňovaní člena.', 500);
}
};

View File

@@ -0,0 +1,414 @@
import { Response } from 'express';
import prisma from '../config/database';
import { successResponse, errorResponse, paginatedResponse, parseQueryInt, getParam, getQueryString } from '../utils/helpers';
import { AuthRequest } from '../middleware/auth.middleware';
import { configService } from '../services/config.service';
async function generateRMANumber(): Promise<string> {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
const startOfDay = new Date(today.setHours(0, 0, 0, 0));
const endOfDay = new Date(today.setHours(23, 59, 59, 999));
const count = await prisma.rMA.count({
where: {
createdAt: {
gte: startOfDay,
lte: endOfDay,
},
},
});
const sequence = String(count + 1).padStart(3, '0');
return `RMA-${year}${month}${day}${sequence}`;
}
export const getRMAs = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const page = parseQueryInt(req.query.page, 1);
const limit = parseQueryInt(req.query.limit, 20);
const skip = (page - 1) * limit;
const search = getQueryString(req, 'search');
const statusId = getQueryString(req, 'statusId');
const customerId = getQueryString(req, 'customerId');
const assignedToId = getQueryString(req, 'assignedToId');
const where = {
...(statusId && { statusId }),
...(customerId && { customerId }),
...(assignedToId && { assignedToId }),
...(search && {
OR: [
{ rmaNumber: { contains: search, mode: 'insensitive' as const } },
{ productName: { contains: search, mode: 'insensitive' as const } },
{ customerName: { contains: search, mode: 'insensitive' as const } },
],
}),
};
const [rmas, total] = await Promise.all([
prisma.rMA.findMany({
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
include: {
status: true,
proposedSolution: true,
customer: { select: { id: true, name: true } },
assignedTo: { select: { id: true, name: true } },
createdBy: { select: { id: true, name: true } },
},
}),
prisma.rMA.count({ where }),
]);
paginatedResponse(res, rmas, total, page, limit);
} catch (error) {
console.error('Error fetching RMAs:', error);
errorResponse(res, 'Chyba pri načítaní reklamácií.', 500);
}
};
export const getRMA = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const rma = await prisma.rMA.findUnique({
where: { id },
include: {
status: true,
proposedSolution: true,
customer: true,
assignedTo: { select: { id: true, name: true, email: true } },
createdBy: { select: { id: true, name: true, email: true } },
approvedBy: { select: { id: true, name: true } },
attachments: {
orderBy: { uploadedAt: 'desc' },
},
statusHistory: {
orderBy: { changedAt: 'desc' },
include: {
changedBy: { select: { id: true, name: true } },
},
},
comments: {
orderBy: { createdAt: 'desc' },
include: {
user: { select: { id: true, name: true } },
},
},
tags: { include: { tag: true } },
},
});
if (!rma) {
errorResponse(res, 'Reklamácia nebola nájdená.', 404);
return;
}
successResponse(res, rma);
} catch (error) {
console.error('Error fetching RMA:', error);
errorResponse(res, 'Chyba pri načítaní reklamácie.', 500);
}
};
export const createRMA = async (req: AuthRequest, res: Response): Promise<void> => {
try {
let { statusId, proposedSolutionId } = req.body;
// Get initial status if not provided
if (!statusId) {
const initialStatus = await configService.getInitialRMAStatus();
if (initialStatus) {
statusId = initialStatus.id;
} else {
errorResponse(res, 'Predvolený status reklamácie nie je nakonfigurovaný.', 500);
return;
}
}
// Get default solution (Assessment) if not provided
if (!proposedSolutionId) {
const solutions = await configService.getRMASolutions();
const defaultSolution = (solutions as { id: string; code: string }[]).find(
(s) => s.code === 'ASSESSMENT'
);
if (defaultSolution) {
proposedSolutionId = defaultSolution.id;
}
}
const rmaNumber = await generateRMANumber();
// Check if customer role requires approval
let requiresApproval = false;
if (req.user?.roleCode === 'CUSTOMER') {
const setting = await configService.getSetting<boolean>('RMA_CUSTOMER_REQUIRES_APPROVAL');
requiresApproval = setting === true;
}
const rma = await prisma.rMA.create({
data: {
rmaNumber,
customerId: req.body.customerId || null,
customerName: req.body.customerName,
customerAddress: req.body.customerAddress,
customerEmail: req.body.customerEmail,
customerPhone: req.body.customerPhone,
customerICO: req.body.customerICO,
submittedBy: req.body.submittedBy,
productName: req.body.productName,
invoiceNumber: req.body.invoiceNumber,
purchaseDate: req.body.purchaseDate ? new Date(req.body.purchaseDate) : null,
productNumber: req.body.productNumber,
serialNumber: req.body.serialNumber,
accessories: req.body.accessories,
issueDescription: req.body.issueDescription,
statusId,
proposedSolutionId,
requiresApproval,
createdById: req.user!.userId,
},
include: {
status: true,
proposedSolution: true,
customer: { select: { id: true, name: true } },
createdBy: { select: { id: true, name: true } },
},
});
// Create initial status history
await prisma.rMAStatusHistory.create({
data: {
rmaId: rma.id,
toStatusId: statusId,
changedById: req.user!.userId,
notes: 'Reklamácia vytvorená',
},
});
if (req.logActivity) {
await req.logActivity('CREATE', 'RMA', rma.id, { rmaNumber });
}
successResponse(res, rma, 'Reklamácia bola vytvorená.', 201);
} catch (error) {
console.error('Error creating RMA:', error);
errorResponse(res, 'Chyba pri vytváraní reklamácie.', 500);
}
};
export const updateRMA = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const existing = await prisma.rMA.findUnique({ where: { id } });
if (!existing) {
errorResponse(res, 'Reklamácia nebola nájdená.', 404);
return;
}
const updateData: Record<string, unknown> = {};
const fields = [
'customerId', 'customerName', 'customerAddress', 'customerEmail',
'customerPhone', 'customerICO', 'submittedBy', 'productName',
'invoiceNumber', 'productNumber', 'serialNumber', 'accessories',
'issueDescription', 'proposedSolutionId', 'receivedLocation',
'internalNotes', 'resolutionNotes', 'assignedToId'
];
for (const field of fields) {
if (req.body[field] !== undefined) {
updateData[field] = req.body[field];
}
}
if (req.body.purchaseDate !== undefined) {
updateData.purchaseDate = req.body.purchaseDate ? new Date(req.body.purchaseDate) : null;
}
if (req.body.receivedDate !== undefined) {
updateData.receivedDate = req.body.receivedDate ? new Date(req.body.receivedDate) : null;
}
if (req.body.resolutionDate !== undefined) {
updateData.resolutionDate = req.body.resolutionDate ? new Date(req.body.resolutionDate) : null;
}
const rma = await prisma.rMA.update({
where: { id },
data: updateData,
include: {
status: true,
proposedSolution: true,
customer: { select: { id: true, name: true } },
assignedTo: { select: { id: true, name: true } },
},
});
if (req.logActivity) {
await req.logActivity('UPDATE', 'RMA', id, updateData);
}
successResponse(res, rma, 'Reklamácia bola aktualizovaná.');
} catch (error) {
console.error('Error updating RMA:', error);
errorResponse(res, 'Chyba pri aktualizácii reklamácie.', 500);
}
};
export const deleteRMA = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const rma = await prisma.rMA.findUnique({ where: { id } });
if (!rma) {
errorResponse(res, 'Reklamácia nebola nájdená.', 404);
return;
}
await prisma.rMA.delete({ where: { id } });
if (req.logActivity) {
await req.logActivity('DELETE', 'RMA', id);
}
successResponse(res, null, 'Reklamácia bola vymazaná.');
} catch (error) {
console.error('Error deleting RMA:', error);
errorResponse(res, 'Chyba pri mazaní reklamácie.', 500);
}
};
export const updateRMAStatus = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const { statusId, notes } = req.body;
const rma = await prisma.rMA.findUnique({
where: { id },
include: { status: true },
});
if (!rma) {
errorResponse(res, 'Reklamácia nebola nájdená.', 404);
return;
}
const newStatus = await prisma.rMAStatus.findUnique({ where: { id: statusId } });
if (!newStatus) {
errorResponse(res, 'Status nebol nájdený.', 404);
return;
}
// Validate transition (basic - can be enhanced with workflow rules)
const currentStatus = rma.status;
if (currentStatus.canTransitionTo) {
const allowedTransitions = currentStatus.canTransitionTo as string[];
if (!allowedTransitions.includes(newStatus.code)) {
errorResponse(
res,
`Prechod zo statusu "${currentStatus.name}" na "${newStatus.name}" nie je povolený.`,
400
);
return;
}
}
const updatedRMA = await prisma.rMA.update({
where: { id },
data: {
statusId,
closedAt: newStatus.isFinal ? new Date() : null,
},
include: { status: true },
});
// Create status history
await prisma.rMAStatusHistory.create({
data: {
rmaId: id,
fromStatusId: rma.statusId,
toStatusId: statusId,
changedById: req.user!.userId,
notes,
},
});
if (req.logActivity) {
await req.logActivity('STATUS_CHANGE', 'RMA', id, {
from: currentStatus.code,
to: newStatus.code,
});
}
successResponse(res, updatedRMA, 'Status reklamácie bol zmenený.');
} catch (error) {
console.error('Error updating RMA status:', error);
errorResponse(res, 'Chyba pri zmene statusu.', 500);
}
};
export const approveRMA = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const rma = await prisma.rMA.update({
where: { id },
data: {
requiresApproval: false,
approvedById: req.user!.userId,
approvedAt: new Date(),
},
include: { status: true },
});
if (req.logActivity) {
await req.logActivity('UPDATE', 'RMA', id, { approved: true });
}
successResponse(res, rma, 'Reklamácia bola schválená.');
} catch (error) {
console.error('Error approving RMA:', error);
errorResponse(res, 'Chyba pri schvaľovaní reklamácie.', 500);
}
};
export const addRMAComment = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const { content } = req.body;
const comment = await prisma.rMAComment.create({
data: {
rmaId: id,
userId: req.user!.userId,
content,
},
include: {
user: { select: { id: true, name: true } },
},
});
successResponse(res, comment, 'Komentár bol pridaný.', 201);
} catch (error) {
console.error('Error adding RMA comment:', error);
errorResponse(res, 'Chyba pri pridávaní komentára.', 500);
}
};
export const generateRMANumberEndpoint = async (_req: AuthRequest, res: Response): Promise<void> => {
try {
const rmaNumber = await generateRMANumber();
successResponse(res, { rmaNumber });
} catch (error) {
console.error('Error generating RMA number:', error);
errorResponse(res, 'Chyba pri generovaní RMA čísla.', 500);
}
};

View File

@@ -0,0 +1,450 @@
import { Response } from 'express';
import prisma from '../config/database';
import { successResponse, errorResponse, getParam, getQueryString } from '../utils/helpers';
import { AuthRequest } from '../middleware/auth.middleware';
import { configService } from '../services/config.service';
// ==================== EQUIPMENT TYPES ====================
export const getEquipmentTypes = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const activeOnly = getQueryString(req, 'activeOnly') !== 'false';
const types = await configService.getEquipmentTypes(activeOnly);
successResponse(res, types);
} catch (error) {
console.error('Error fetching equipment types:', error);
errorResponse(res, 'Chyba pri načítaní typov zariadení.', 500);
}
};
export const createEquipmentType = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const type = await prisma.equipmentType.create({ data: req.body });
configService.clearCacheKey('equipment_types');
successResponse(res, type, 'Typ zariadenia bol vytvorený.', 201);
} catch (error) {
console.error('Error creating equipment type:', error);
errorResponse(res, 'Chyba pri vytváraní typu zariadenia.', 500);
}
};
export const updateEquipmentType = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const type = await prisma.equipmentType.update({ where: { id }, data: req.body });
configService.clearCacheKey('equipment_types');
successResponse(res, type, 'Typ zariadenia bol aktualizovaný.');
} catch (error) {
console.error('Error updating equipment type:', error);
errorResponse(res, 'Chyba pri aktualizácii typu zariadenia.', 500);
}
};
export const deleteEquipmentType = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
await prisma.equipmentType.update({ where: { id }, data: { active: false } });
configService.clearCacheKey('equipment_types');
successResponse(res, null, 'Typ zariadenia bol deaktivovaný.');
} catch (error) {
console.error('Error deleting equipment type:', error);
errorResponse(res, 'Chyba pri mazaní typu zariadenia.', 500);
}
};
// ==================== REVISION TYPES ====================
export const getRevisionTypes = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const activeOnly = getQueryString(req, 'activeOnly') !== 'false';
const types = await configService.getRevisionTypes(activeOnly);
successResponse(res, types);
} catch (error) {
console.error('Error fetching revision types:', error);
errorResponse(res, 'Chyba pri načítaní typov revízií.', 500);
}
};
export const createRevisionType = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const type = await prisma.revisionType.create({ data: req.body });
configService.clearCacheKey('revision_types');
successResponse(res, type, 'Typ revízie bol vytvorený.', 201);
} catch (error) {
console.error('Error creating revision type:', error);
errorResponse(res, 'Chyba pri vytváraní typu revízie.', 500);
}
};
export const updateRevisionType = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const type = await prisma.revisionType.update({ where: { id }, data: req.body });
configService.clearCacheKey('revision_types');
successResponse(res, type, 'Typ revízie bol aktualizovaný.');
} catch (error) {
console.error('Error updating revision type:', error);
errorResponse(res, 'Chyba pri aktualizácii typu revízie.', 500);
}
};
export const deleteRevisionType = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
await prisma.revisionType.update({ where: { id }, data: { active: false } });
configService.clearCacheKey('revision_types');
successResponse(res, null, 'Typ revízie bol deaktivovaný.');
} catch (error) {
console.error('Error deleting revision type:', error);
errorResponse(res, 'Chyba pri mazaní typu revízie.', 500);
}
};
// ==================== RMA STATUSES ====================
export const getRMAStatuses = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const activeOnly = getQueryString(req, 'activeOnly') !== 'false';
const statuses = await configService.getRMAStatuses(activeOnly);
successResponse(res, statuses);
} catch (error) {
console.error('Error fetching RMA statuses:', error);
errorResponse(res, 'Chyba pri načítaní statusov reklamácií.', 500);
}
};
export const createRMAStatus = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const status = await prisma.rMAStatus.create({ data: req.body });
configService.clearCacheKey('rma_statuses');
successResponse(res, status, 'Status reklamácie bol vytvorený.', 201);
} catch (error) {
console.error('Error creating RMA status:', error);
errorResponse(res, 'Chyba pri vytváraní statusu reklamácie.', 500);
}
};
export const updateRMAStatus = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const status = await prisma.rMAStatus.update({ where: { id }, data: req.body });
configService.clearCacheKey('rma_statuses');
successResponse(res, status, 'Status reklamácie bol aktualizovaný.');
} catch (error) {
console.error('Error updating RMA status:', error);
errorResponse(res, 'Chyba pri aktualizácii statusu reklamácie.', 500);
}
};
export const deleteRMAStatus = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
await prisma.rMAStatus.update({ where: { id }, data: { active: false } });
configService.clearCacheKey('rma_statuses');
successResponse(res, null, 'Status reklamácie bol deaktivovaný.');
} catch (error) {
console.error('Error deleting RMA status:', error);
errorResponse(res, 'Chyba pri mazaní statusu reklamácie.', 500);
}
};
// ==================== RMA SOLUTIONS ====================
export const getRMASolutions = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const activeOnly = getQueryString(req, 'activeOnly') !== 'false';
const solutions = await configService.getRMASolutions(activeOnly);
successResponse(res, solutions);
} catch (error) {
console.error('Error fetching RMA solutions:', error);
errorResponse(res, 'Chyba pri načítaní riešení reklamácií.', 500);
}
};
export const createRMASolution = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const solution = await prisma.rMASolution.create({ data: req.body });
configService.clearCacheKey('rma_solutions');
successResponse(res, solution, 'Riešenie reklamácie bolo vytvorené.', 201);
} catch (error) {
console.error('Error creating RMA solution:', error);
errorResponse(res, 'Chyba pri vytváraní riešenia reklamácie.', 500);
}
};
export const updateRMASolution = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const solution = await prisma.rMASolution.update({ where: { id }, data: req.body });
configService.clearCacheKey('rma_solutions');
successResponse(res, solution, 'Riešenie reklamácie bolo aktualizované.');
} catch (error) {
console.error('Error updating RMA solution:', error);
errorResponse(res, 'Chyba pri aktualizácii riešenia reklamácie.', 500);
}
};
export const deleteRMASolution = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
await prisma.rMASolution.update({ where: { id }, data: { active: false } });
configService.clearCacheKey('rma_solutions');
successResponse(res, null, 'Riešenie reklamácie bolo deaktivované.');
} catch (error) {
console.error('Error deleting RMA solution:', error);
errorResponse(res, 'Chyba pri mazaní riešenia reklamácie.', 500);
}
};
// ==================== TASK STATUSES ====================
export const getTaskStatuses = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const activeOnly = getQueryString(req, 'activeOnly') !== 'false';
const statuses = await configService.getTaskStatuses(activeOnly);
successResponse(res, statuses);
} catch (error) {
console.error('Error fetching task statuses:', error);
errorResponse(res, 'Chyba pri načítaní statusov úloh.', 500);
}
};
export const createTaskStatus = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const status = await prisma.taskStatus.create({ data: req.body });
configService.clearCacheKey('task_statuses');
successResponse(res, status, 'Status úlohy bol vytvorený.', 201);
} catch (error) {
console.error('Error creating task status:', error);
errorResponse(res, 'Chyba pri vytváraní statusu úlohy.', 500);
}
};
export const updateTaskStatus = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const status = await prisma.taskStatus.update({ where: { id }, data: req.body });
configService.clearCacheKey('task_statuses');
successResponse(res, status, 'Status úlohy bol aktualizovaný.');
} catch (error) {
console.error('Error updating task status:', error);
errorResponse(res, 'Chyba pri aktualizácii statusu úlohy.', 500);
}
};
export const deleteTaskStatus = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
await prisma.taskStatus.update({ where: { id }, data: { active: false } });
configService.clearCacheKey('task_statuses');
successResponse(res, null, 'Status úlohy bol deaktivovaný.');
} catch (error) {
console.error('Error deleting task status:', error);
errorResponse(res, 'Chyba pri mazaní statusu úlohy.', 500);
}
};
// ==================== PRIORITIES ====================
export const getPriorities = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const activeOnly = getQueryString(req, 'activeOnly') !== 'false';
const priorities = await configService.getPriorities(activeOnly);
successResponse(res, priorities);
} catch (error) {
console.error('Error fetching priorities:', error);
errorResponse(res, 'Chyba pri načítaní priorít.', 500);
}
};
export const createPriority = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const priority = await prisma.priority.create({ data: req.body });
configService.clearCacheKey('priorities');
successResponse(res, priority, 'Priorita bola vytvorená.', 201);
} catch (error) {
console.error('Error creating priority:', error);
errorResponse(res, 'Chyba pri vytváraní priority.', 500);
}
};
export const updatePriority = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const priority = await prisma.priority.update({ where: { id }, data: req.body });
configService.clearCacheKey('priorities');
successResponse(res, priority, 'Priorita bola aktualizovaná.');
} catch (error) {
console.error('Error updating priority:', error);
errorResponse(res, 'Chyba pri aktualizácii priority.', 500);
}
};
export const deletePriority = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
await prisma.priority.update({ where: { id }, data: { active: false } });
configService.clearCacheKey('priorities');
successResponse(res, null, 'Priorita bola deaktivovaná.');
} catch (error) {
console.error('Error deleting priority:', error);
errorResponse(res, 'Chyba pri mazaní priority.', 500);
}
};
// ==================== TAGS ====================
export const getTags = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const entityType = getQueryString(req, 'entityType');
const activeOnly = getQueryString(req, 'activeOnly') !== 'false';
const tags = await configService.getTags(entityType, activeOnly);
successResponse(res, tags);
} catch (error) {
console.error('Error fetching tags:', error);
errorResponse(res, 'Chyba pri načítaní tagov.', 500);
}
};
export const createTag = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const tag = await prisma.tag.create({ data: req.body });
configService.clearCacheKey('tags');
successResponse(res, tag, 'Tag bol vytvorený.', 201);
} catch (error) {
console.error('Error creating tag:', error);
errorResponse(res, 'Chyba pri vytváraní tagu.', 500);
}
};
export const updateTag = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const tag = await prisma.tag.update({ where: { id }, data: req.body });
configService.clearCacheKey('tags');
successResponse(res, tag, 'Tag bol aktualizovaný.');
} catch (error) {
console.error('Error updating tag:', error);
errorResponse(res, 'Chyba pri aktualizácii tagu.', 500);
}
};
export const deleteTag = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
await prisma.tag.update({ where: { id }, data: { active: false } });
configService.clearCacheKey('tags');
successResponse(res, null, 'Tag bol deaktivovaný.');
} catch (error) {
console.error('Error deleting tag:', error);
errorResponse(res, 'Chyba pri mazaní tagu.', 500);
}
};
// ==================== USER ROLES ====================
export const getUserRoles = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const activeOnly = getQueryString(req, 'activeOnly') !== 'false';
const roles = await configService.getUserRoles(activeOnly);
successResponse(res, roles);
} catch (error) {
console.error('Error fetching user roles:', error);
errorResponse(res, 'Chyba pri načítaní rolí.', 500);
}
};
export const createUserRole = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const role = await prisma.userRole.create({ data: req.body });
configService.clearCacheKey('user_roles');
successResponse(res, role, 'Rola bola vytvorená.', 201);
} catch (error) {
console.error('Error creating user role:', error);
errorResponse(res, 'Chyba pri vytváraní roly.', 500);
}
};
export const updateUserRole = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const role = await prisma.userRole.update({ where: { id }, data: req.body });
configService.clearCacheKey('user_roles');
successResponse(res, role, 'Rola bola aktualizovaná.');
} catch (error) {
console.error('Error updating user role:', error);
errorResponse(res, 'Chyba pri aktualizácii roly.', 500);
}
};
export const deleteUserRole = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
await prisma.userRole.update({ where: { id }, data: { active: false } });
configService.clearCacheKey('user_roles');
successResponse(res, null, 'Rola bola deaktivovaná.');
} catch (error) {
console.error('Error deleting user role:', error);
errorResponse(res, 'Chyba pri mazaní roly.', 500);
}
};
// ==================== SYSTEM SETTINGS ====================
export const getSystemSettings = async (_req: AuthRequest, res: Response): Promise<void> => {
try {
const settings = await prisma.systemSetting.findMany({
orderBy: [{ category: 'asc' }, { key: 'asc' }],
});
successResponse(res, settings);
} catch (error) {
console.error('Error fetching system settings:', error);
errorResponse(res, 'Chyba pri načítaní nastavení.', 500);
}
};
export const getSystemSetting = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const key = getParam(req, 'key');
const setting = await prisma.systemSetting.findUnique({ where: { key } });
if (!setting) {
errorResponse(res, 'Nastavenie nebolo nájdené.', 404);
return;
}
successResponse(res, setting);
} catch (error) {
console.error('Error fetching system setting:', error);
errorResponse(res, 'Chyba pri načítaní nastavenia.', 500);
}
};
export const updateSystemSetting = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const key = getParam(req, 'key');
const { value } = req.body;
const setting = await prisma.systemSetting.update({
where: { key },
data: { value },
});
configService.clearCacheKey(`setting_${key}`);
successResponse(res, setting, 'Nastavenie bolo aktualizované.');
} catch (error) {
console.error('Error updating system setting:', error);
errorResponse(res, 'Chyba pri aktualizácii nastavenia.', 500);
}
};
export const getSystemSettingsByCategory = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const category = getParam(req, 'category');
const settings = await configService.getSettingsByCategory(category);
successResponse(res, settings);
} catch (error) {
console.error('Error fetching system settings by category:', error);
errorResponse(res, 'Chyba pri načítaní nastavení.', 500);
}
};

View File

@@ -0,0 +1,400 @@
import { Response } from 'express';
import prisma from '../config/database';
import { successResponse, errorResponse, paginatedResponse, parseQueryInt, getParam, getQueryString } from '../utils/helpers';
import { AuthRequest } from '../middleware/auth.middleware';
import { configService } from '../services/config.service';
export const getTasks = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const page = parseQueryInt(req.query.page, 1);
const limit = parseQueryInt(req.query.limit, 20);
const skip = (page - 1) * limit;
const search = getQueryString(req, 'search');
const projectId = getQueryString(req, 'projectId');
const statusId = getQueryString(req, 'statusId');
const priorityId = getQueryString(req, 'priorityId');
const createdById = getQueryString(req, 'createdById');
const assigneeId = getQueryString(req, 'assigneeId');
const where = {
...(projectId && { projectId }),
...(statusId && { statusId }),
...(priorityId && { priorityId }),
...(createdById && { createdById }),
...(assigneeId && {
assignees: { some: { userId: assigneeId } },
}),
...(search && {
OR: [
{ title: { contains: search, mode: 'insensitive' as const } },
{ description: { contains: search, mode: 'insensitive' as const } },
],
}),
};
const [tasks, total] = await Promise.all([
prisma.task.findMany({
where,
skip,
take: limit,
orderBy: [{ priority: { level: 'desc' } }, { createdAt: 'desc' }],
include: {
status: true,
priority: true,
project: { select: { id: true, name: true } },
createdBy: { select: { id: true, name: true } },
assignees: {
include: { user: { select: { id: true, name: true } } },
},
_count: { select: { subTasks: true, comments: true } },
},
}),
prisma.task.count({ where }),
]);
paginatedResponse(res, tasks, total, page, limit);
} catch (error) {
console.error('Error fetching tasks:', error);
errorResponse(res, 'Chyba pri načítaní úloh.', 500);
}
};
export const getTask = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const task = await prisma.task.findUnique({
where: { id },
include: {
status: true,
priority: true,
project: { select: { id: true, name: true } },
parent: { select: { id: true, title: true } },
subTasks: {
include: {
status: true,
assignees: { include: { user: { select: { id: true, name: true } } } },
},
},
createdBy: { select: { id: true, name: true, email: true } },
assignees: {
include: { user: { select: { id: true, name: true, email: true } } },
},
comments: {
orderBy: { createdAt: 'desc' },
take: 10,
},
tags: { include: { tag: true } },
},
});
if (!task) {
errorResponse(res, 'Úloha nebola nájdená.', 404);
return;
}
successResponse(res, task);
} catch (error) {
console.error('Error fetching task:', error);
errorResponse(res, 'Chyba pri načítaní úlohy.', 500);
}
};
export const createTask = async (req: AuthRequest, res: Response): Promise<void> => {
try {
let { statusId, priorityId } = req.body;
// Get defaults if not provided
if (!statusId) {
const initialStatus = await configService.getInitialTaskStatus();
if (initialStatus) {
statusId = initialStatus.id;
} else {
// Fallback: vezmi prvý aktívny status
const allStatuses = await configService.getTaskStatuses();
const statuses = allStatuses as { id: string }[];
if (statuses.length > 0) {
statusId = statuses[0].id;
} else {
errorResponse(res, 'Žiadny status nie je nakonfigurovaný. Spustite seed.', 500);
return;
}
}
}
if (!priorityId) {
const defaultPriority = await configService.getDefaultPriority();
if (defaultPriority) {
priorityId = defaultPriority.id;
} else {
// Fallback: vezmi prvú aktívnu prioritu
const allPriorities = await configService.getPriorities();
const priorities = allPriorities as { id: string }[];
if (priorities.length > 0) {
priorityId = priorities[0].id;
} else {
errorResponse(res, 'Žiadna priorita nie je nakonfigurovaná. Spustite seed.', 500);
return;
}
}
}
const task = await prisma.task.create({
data: {
title: req.body.title,
description: req.body.description,
projectId: req.body.projectId || null,
parentId: req.body.parentId || null,
statusId,
priorityId,
deadline: req.body.deadline ? new Date(req.body.deadline) : null,
createdById: req.user!.userId,
},
include: {
status: true,
priority: true,
project: { select: { id: true, name: true } },
createdBy: { select: { id: true, name: true } },
},
});
// Add assignees if provided
if (req.body.assigneeIds && req.body.assigneeIds.length > 0) {
await prisma.taskAssignee.createMany({
data: req.body.assigneeIds.map((userId: string) => ({
taskId: task.id,
userId,
})),
});
}
if (req.logActivity) {
await req.logActivity('CREATE', 'Task', task.id, { title: task.title });
}
successResponse(res, task, 'Úloha bola vytvorená.', 201);
} catch (error) {
console.error('Error creating task:', error);
errorResponse(res, 'Chyba pri vytváraní úlohy.', 500);
}
};
export const updateTask = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const existing = await prisma.task.findUnique({ where: { id } });
if (!existing) {
errorResponse(res, 'Úloha nebola nájdená.', 404);
return;
}
const updateData: Record<string, unknown> = {};
if (req.body.title) updateData.title = req.body.title;
if (req.body.description !== undefined) updateData.description = req.body.description;
if (req.body.projectId !== undefined) updateData.projectId = req.body.projectId || null;
if (req.body.parentId !== undefined) updateData.parentId = req.body.parentId || null;
if (req.body.statusId) updateData.statusId = req.body.statusId;
if (req.body.priorityId) updateData.priorityId = req.body.priorityId;
if (req.body.deadline !== undefined) {
updateData.deadline = req.body.deadline ? new Date(req.body.deadline) : null;
}
const task = await prisma.task.update({
where: { id },
data: updateData,
include: {
status: true,
priority: true,
project: { select: { id: true, name: true } },
assignees: { include: { user: { select: { id: true, name: true } } } },
},
});
// Update assignees if provided
if (req.body.assigneeIds !== undefined) {
// Remove all current assignees
await prisma.taskAssignee.deleteMany({ where: { taskId: id } });
// Add new assignees
if (req.body.assigneeIds.length > 0) {
await prisma.taskAssignee.createMany({
data: req.body.assigneeIds.map((userId: string) => ({
taskId: id,
userId,
})),
});
}
}
// Re-fetch task with updated assignees
const updatedTask = await prisma.task.findUnique({
where: { id },
include: {
status: true,
priority: true,
project: { select: { id: true, name: true } },
assignees: { include: { user: { select: { id: true, name: true } } } },
},
});
if (req.logActivity) {
await req.logActivity('UPDATE', 'Task', id, updateData);
}
successResponse(res, updatedTask, 'Úloha bola aktualizovaná.');
} catch (error) {
console.error('Error updating task:', error);
errorResponse(res, 'Chyba pri aktualizácii úlohy.', 500);
}
};
export const deleteTask = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const task = await prisma.task.findUnique({ where: { id } });
if (!task) {
errorResponse(res, 'Úloha nebola nájdená.', 404);
return;
}
await prisma.task.delete({ where: { id } });
if (req.logActivity) {
await req.logActivity('DELETE', 'Task', id);
}
successResponse(res, null, 'Úloha bola vymazaná.');
} catch (error) {
console.error('Error deleting task:', error);
errorResponse(res, 'Chyba pri mazaní úlohy.', 500);
}
};
export const updateTaskStatus = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const { statusId } = req.body;
const status = await prisma.taskStatus.findUnique({ where: { id: statusId } });
const task = await prisma.task.update({
where: { id },
data: {
statusId,
completedAt: status?.isFinal ? new Date() : null,
},
include: { status: true },
});
if (req.logActivity) {
await req.logActivity('STATUS_CHANGE', 'Task', id, { statusId });
}
successResponse(res, task, 'Status úlohy bol zmenený.');
} catch (error) {
console.error('Error updating task status:', error);
errorResponse(res, 'Chyba pri zmene statusu.', 500);
}
};
export const addTaskAssignee = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const { userId } = req.body;
const assignee = await prisma.taskAssignee.create({
data: { taskId: id, userId },
include: { user: { select: { id: true, name: true, email: true } } },
});
successResponse(res, assignee, 'Priraďovateľ bol pridaný.', 201);
} catch (error) {
console.error('Error adding task assignee:', error);
errorResponse(res, 'Chyba pri pridávaní priraďovateľa.', 500);
}
};
export const removeTaskAssignee = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const userId = getParam(req, 'userId');
await prisma.taskAssignee.delete({
where: { taskId_userId: { taskId: id, userId } },
});
successResponse(res, null, 'Priraďovateľ bol odstránený.');
} catch (error) {
console.error('Error removing task assignee:', error);
errorResponse(res, 'Chyba pri odstraňovaní priraďovateľa.', 500);
}
};
export const getTaskComments = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const comments = await prisma.comment.findMany({
where: { taskId: id },
orderBy: { createdAt: 'desc' },
include: {
user: { select: { id: true, name: true } },
},
});
successResponse(res, comments);
} catch (error) {
console.error('Error fetching task comments:', error);
errorResponse(res, 'Chyba pri načítaní komentárov.', 500);
}
};
export const addTaskComment = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const { content } = req.body;
const userId = req.user!.userId;
// Načítať úlohu s assignees pre kontrolu oprávnení
const task = await prisma.task.findUnique({
where: { id },
include: {
assignees: { select: { userId: true } },
},
});
if (!task) {
errorResponse(res, 'Úloha nebola nájdená.', 404);
return;
}
// Kontrola oprávnení - len autor alebo priradený môže komentovať
const isCreator = task.createdById === userId;
const isAssignee = task.assignees.some(a => a.userId === userId);
if (!isCreator && !isAssignee) {
errorResponse(res, 'Nemáte oprávnenie komentovať túto úlohu.', 403);
return;
}
const comment = await prisma.comment.create({
data: {
taskId: id,
userId,
content,
},
include: {
user: { select: { id: true, name: true } },
},
});
successResponse(res, comment, 'Komentár bol pridaný.', 201);
} catch (error) {
console.error('Error adding task comment:', error);
errorResponse(res, 'Chyba pri pridávaní komentára.', 500);
}
};

View File

@@ -0,0 +1,272 @@
import { Response } from 'express';
import bcrypt from 'bcryptjs';
import prisma from '../config/database';
import { successResponse, errorResponse, paginatedResponse, parseQueryInt, getParam, getQueryString } from '../utils/helpers';
import { AuthRequest } from '../middleware/auth.middleware';
// Jednoduchý zoznam aktívnych používateľov (pre select/dropdown)
// Podporuje server-side vyhľadávanie pre lepší výkon pri veľkom počte používateľov
export const getUsersSimple = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const search = getQueryString(req, 'search');
const limit = parseQueryInt(req.query.limit, 50); // Default 50, max 100
const actualLimit = Math.min(limit, 100);
const where = {
active: true,
...(search && {
OR: [
{ name: { contains: search, mode: 'insensitive' as const } },
{ email: { contains: search, mode: 'insensitive' as const } },
],
}),
};
const users = await prisma.user.findMany({
where,
take: actualLimit,
orderBy: { name: 'asc' },
select: {
id: true,
name: true,
email: true,
},
});
successResponse(res, users);
} catch (error) {
console.error('Error fetching users simple:', error);
errorResponse(res, 'Chyba pri načítaní používateľov.', 500);
}
};
export const getUsers = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const page = parseQueryInt(req.query.page, 1);
const limit = parseQueryInt(req.query.limit, 20);
const skip = (page - 1) * limit;
const search = getQueryString(req, 'search');
const active = getQueryString(req, 'active');
const roleId = getQueryString(req, 'roleId');
const where = {
...(active !== undefined && { active: active === 'true' }),
...(roleId && { roleId }),
...(search && {
OR: [
{ name: { contains: search, mode: 'insensitive' as const } },
{ email: { contains: search, mode: 'insensitive' as const } },
],
}),
};
const [users, total] = await Promise.all([
prisma.user.findMany({
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
select: {
id: true,
email: true,
name: true,
active: true,
createdAt: true,
updatedAt: true,
role: {
select: {
id: true,
code: true,
name: true,
},
},
},
}),
prisma.user.count({ where }),
]);
paginatedResponse(res, users, total, page, limit);
} catch (error) {
console.error('Error fetching users:', error);
errorResponse(res, 'Chyba pri načítaní používateľov.', 500);
}
};
export const getUser = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const user = await prisma.user.findUnique({
where: { id },
select: {
id: true,
email: true,
name: true,
active: true,
createdAt: true,
updatedAt: true,
role: {
select: {
id: true,
code: true,
name: true,
permissions: true,
},
},
},
});
if (!user) {
errorResponse(res, 'Používateľ nebol nájdený.', 404);
return;
}
successResponse(res, user);
} catch (error) {
console.error('Error fetching user:', error);
errorResponse(res, 'Chyba pri načítaní používateľa.', 500);
}
};
export const updateUser = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const { email, name, active, password } = req.body;
const existingUser = await prisma.user.findUnique({ where: { id } });
if (!existingUser) {
errorResponse(res, 'Používateľ nebol nájdený.', 404);
return;
}
// Check email uniqueness if changing
if (email && email !== existingUser.email) {
const emailExists = await prisma.user.findUnique({ where: { email } });
if (emailExists) {
errorResponse(res, 'Email je už používaný.', 409);
return;
}
}
const updateData: Record<string, unknown> = {};
if (email) updateData.email = email;
if (name) updateData.name = name;
if (active !== undefined) updateData.active = active;
if (password) updateData.password = await bcrypt.hash(password, 10);
const user = await prisma.user.update({
where: { id },
data: updateData,
select: {
id: true,
email: true,
name: true,
active: true,
role: {
select: {
id: true,
code: true,
name: true,
},
},
},
});
if (req.logActivity) {
await req.logActivity('UPDATE', 'User', id, updateData);
}
successResponse(res, user, 'Používateľ bol aktualizovaný.');
} catch (error) {
console.error('Error updating user:', error);
errorResponse(res, 'Chyba pri aktualizácii používateľa.', 500);
}
};
export const deleteUser = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
// Prevent self-deletion
if (req.user?.userId === id) {
errorResponse(res, 'Nemôžete vymazať vlastný účet.', 400);
return;
}
const user = await prisma.user.findUnique({ where: { id } });
if (!user) {
errorResponse(res, 'Používateľ nebol nájdený.', 404);
return;
}
// Soft delete - just deactivate
await prisma.user.update({
where: { id },
data: { active: false },
});
if (req.logActivity) {
await req.logActivity('DELETE', 'User', id);
}
successResponse(res, null, 'Používateľ bol deaktivovaný.');
} catch (error) {
console.error('Error deleting user:', error);
errorResponse(res, 'Chyba pri mazaní používateľa.', 500);
}
};
export const updateUserRole = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const id = getParam(req, 'id');
const { roleId } = req.body;
// Prevent changing own role
if (req.user?.userId === id) {
errorResponse(res, 'Nemôžete zmeniť vlastnú rolu.', 400);
return;
}
const user = await prisma.user.findUnique({ where: { id } });
if (!user) {
errorResponse(res, 'Používateľ nebol nájdený.', 404);
return;
}
const role = await prisma.userRole.findUnique({ where: { id: roleId } });
if (!role) {
errorResponse(res, 'Rola neexistuje.', 404);
return;
}
const updatedUser = await prisma.user.update({
where: { id },
data: { roleId },
select: {
id: true,
email: true,
name: true,
role: {
select: {
id: true,
code: true,
name: true,
},
},
},
});
if (req.logActivity) {
await req.logActivity('UPDATE', 'User', id, { roleId, roleName: role.name });
}
successResponse(res, updatedUser, 'Rola používateľa bola zmenená.');
} catch (error) {
console.error('Error updating user role:', error);
errorResponse(res, 'Chyba pri zmene roly.', 500);
}
};