feat: Add services, company documents, company timesheet export

- Add services table and CRUD endpoints (/api/services)
- Add company documents upload/download functionality
- Add company timesheet XLSX export endpoint
- Remove admin requirement from event routes (all authenticated users can manage events)
- Add service validators

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
richardtekula
2026-01-17 18:45:01 +01:00
parent b542d1d635
commit 514b6c8a92
13 changed files with 866 additions and 11 deletions

View File

@@ -0,0 +1,88 @@
import * as companyDocumentService from '../services/company-document.service.js';
/**
* Get all documents for a company
* GET /api/companies/:companyId/documents
*/
export const getDocuments = async (req, res, next) => {
try {
const { companyId } = req.params;
const documents = await companyDocumentService.getDocumentsByCompanyId(companyId);
res.status(200).json({
success: true,
count: documents.length,
data: documents,
});
} catch (error) {
next(error);
}
};
/**
* Upload a document for a company
* POST /api/companies/:companyId/documents
*/
export const uploadDocument = async (req, res, next) => {
try {
const { companyId } = req.params;
const userId = req.userId;
const { description } = req.body;
const document = await companyDocumentService.uploadDocument({
companyId,
userId,
file: req.file,
description,
});
res.status(201).json({
success: true,
data: document,
message: 'Dokument bol nahraný',
});
} catch (error) {
next(error);
}
};
/**
* Download a document
* GET /api/companies/:companyId/documents/:docId/download
*/
export const downloadDocument = async (req, res, next) => {
try {
const { companyId, docId } = req.params;
const { filePath, fileName, fileType } = await companyDocumentService.getDocumentForDownload(
companyId,
docId
);
res.setHeader('Content-Type', fileType);
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"`);
res.sendFile(filePath);
} catch (error) {
next(error);
}
};
/**
* Delete a document
* DELETE /api/companies/:companyId/documents/:docId
*/
export const deleteDocument = async (req, res, next) => {
try {
const { companyId, docId } = req.params;
const result = await companyDocumentService.deleteDocument(companyId, docId);
res.status(200).json({
success: true,
message: result.message,
});
} catch (error) {
next(error);
}
};

View File

@@ -0,0 +1,101 @@
import * as serviceService from '../services/service.service.js';
/**
* Get all services
* GET /api/services
*/
export const getAllServices = async (req, res, next) => {
try {
const services = await serviceService.getAllServices();
res.status(200).json({
success: true,
count: services.length,
data: services,
});
} catch (error) {
next(error);
}
};
/**
* Get service by ID
* GET /api/services/:serviceId
*/
export const getServiceById = async (req, res, next) => {
try {
const { serviceId } = req.params;
const service = await serviceService.getServiceById(serviceId);
res.status(200).json({
success: true,
data: service,
});
} catch (error) {
next(error);
}
};
/**
* Create new service
* POST /api/services
* Body: { name, price, description }
*/
export const createService = async (req, res, next) => {
try {
const userId = req.userId;
const data = req.body;
const service = await serviceService.createService(userId, data);
res.status(201).json({
success: true,
data: service,
message: 'Služba bola vytvorená',
});
} catch (error) {
next(error);
}
};
/**
* Update service
* PUT /api/services/:serviceId
* Body: { name, price, description }
*/
export const updateService = async (req, res, next) => {
try {
const { serviceId } = req.params;
const data = req.body;
const service = await serviceService.updateService(serviceId, data);
res.status(200).json({
success: true,
data: service,
message: 'Služba bola aktualizovaná',
});
} catch (error) {
next(error);
}
};
/**
* Delete service
* DELETE /api/services/:serviceId
*/
export const deleteService = async (req, res, next) => {
try {
const { serviceId } = req.params;
const result = await serviceService.deleteService(serviceId);
res.status(200).json({
success: true,
message: result.message,
});
} catch (error) {
next(error);
}
};

View File

@@ -325,3 +325,37 @@ export const getMonthlyStats = async (req, res, next) => {
next(error);
}
};
/**
* Generate timesheet for a specific company
* POST /api/time-tracking/generate-company-timesheet
* Body: { year, month, companyId }
*/
export const generateCompanyTimesheet = async (req, res, next) => {
try {
const userId = req.userId;
const { year, month, companyId } = req.body;
if (!year || !month || !companyId) {
return res.status(400).json({
success: false,
message: 'Year, month and companyId are required',
});
}
const result = await timeTrackingService.generateCompanyTimesheet(
userId,
parseInt(year),
parseInt(month),
companyId
);
res.status(201).json({
success: true,
data: result,
message: `Timesheet pre firmu ${result.companyName} bol vygenerovaný`,
});
} catch (error) {
next(error);
}
};