Initial commit: TimeClock v3 - Node.js/Vue.js Zeiterfassung
Features: - Backend: Node.js/Express mit MySQL/MariaDB - Frontend: Vue.js 3 mit Composition API - UTC-Zeithandling für korrekte Zeiterfassung - Timewish-basierte Überstundenberechnung - Wochenübersicht mit Urlaubs-/Krankheits-/Feiertagshandling - Bereinigtes Arbeitsende (Generell/Woche) - Überstunden-Offset für historische Daten - Fixed Layout mit scrollbarem Content - Kompakte UI mit grünem Theme
This commit is contained in:
195
backend/src/repositories/UserRepository.js
Normal file
195
backend/src/repositories/UserRepository.js
Normal file
@@ -0,0 +1,195 @@
|
||||
const database = require('../config/database');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* Repository für User-Datenbankzugriff
|
||||
* Verwendet Sequelize ORM
|
||||
*/
|
||||
class UserRepository {
|
||||
/**
|
||||
* Alle Benutzer abrufen
|
||||
* @returns {Promise<Array>} Liste aller Benutzer
|
||||
*/
|
||||
async findAll() {
|
||||
const { User, State } = database.getModels();
|
||||
|
||||
return await User.findAll({
|
||||
include: [{
|
||||
model: State,
|
||||
as: 'state',
|
||||
required: false
|
||||
}],
|
||||
order: [['full_name', 'ASC']]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Benutzer anhand der ID abrufen
|
||||
* @param {number} id - Benutzer-ID
|
||||
* @returns {Promise<Object|null>} Benutzer oder null
|
||||
*/
|
||||
async findById(id) {
|
||||
const { User, State } = database.getModels();
|
||||
|
||||
return await User.findByPk(id, {
|
||||
include: [{
|
||||
model: State,
|
||||
as: 'state',
|
||||
required: false
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Benutzer anhand der E-Mail abrufen
|
||||
* @param {string} email - E-Mail-Adresse
|
||||
* @returns {Promise<Object|null>} Benutzer oder null
|
||||
*/
|
||||
async findByEmail(email) {
|
||||
const { User, AuthInfo } = database.getModels();
|
||||
|
||||
return await User.findOne({
|
||||
include: [{
|
||||
model: AuthInfo,
|
||||
as: 'authInfo',
|
||||
where: { email },
|
||||
required: true
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wochenarbeitszeit für Benutzer abrufen
|
||||
* @param {number} userId - Benutzer-ID
|
||||
* @param {Date} date - Datum (optional, Standard: heute)
|
||||
* @returns {Promise<Object|null>} Wochenarbeitszeit-Einstellung
|
||||
*/
|
||||
async getWeeklyWorktime(userId, date = new Date()) {
|
||||
const { WeeklyWorktime } = database.getModels();
|
||||
|
||||
const dateStr = date.toISOString().split('T')[0];
|
||||
|
||||
return await WeeklyWorktime.findOne({
|
||||
where: {
|
||||
user_id: userId,
|
||||
[Op.or]: [
|
||||
{ starting_from: null },
|
||||
{ starting_from: { [Op.lte]: dateStr } }
|
||||
],
|
||||
[Op.or]: [
|
||||
{ ends_at: null },
|
||||
{ ends_at: { [Op.gte]: dateStr } }
|
||||
]
|
||||
},
|
||||
order: [['starting_from', 'DESC']]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Benutzer erstellen
|
||||
* @param {Object} userData - Benutzerdaten
|
||||
* @returns {Promise<Object>} Erstellter Benutzer
|
||||
*/
|
||||
async create(userData) {
|
||||
const { User } = database.getModels();
|
||||
|
||||
const {
|
||||
full_name,
|
||||
role = 0,
|
||||
daily_hours = 8,
|
||||
week_hours = 40,
|
||||
week_workdays = 5,
|
||||
state_id = null,
|
||||
preferred_title_type = 0
|
||||
} = userData;
|
||||
|
||||
return await User.create({
|
||||
version: 0,
|
||||
full_name,
|
||||
role,
|
||||
daily_hours,
|
||||
week_hours,
|
||||
week_workdays,
|
||||
state_id,
|
||||
preferred_title_type,
|
||||
last_change: new Date()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Benutzer aktualisieren
|
||||
* @param {number} id - Benutzer-ID
|
||||
* @param {Object} updateData - Zu aktualisierende Daten
|
||||
* @returns {Promise<Object>} Aktualisierter Benutzer
|
||||
*/
|
||||
async update(id, updateData) {
|
||||
const { User } = database.getModels();
|
||||
|
||||
const user = await User.findByPk(id);
|
||||
if (!user) {
|
||||
throw new Error(`Benutzer mit ID ${id} nicht gefunden`);
|
||||
}
|
||||
|
||||
const allowedFields = [
|
||||
'full_name', 'role', 'daily_hours', 'week_hours',
|
||||
'week_workdays', 'state_id', 'preferred_title_type'
|
||||
];
|
||||
|
||||
const updates = {};
|
||||
allowedFields.forEach(field => {
|
||||
if (updateData[field] !== undefined) {
|
||||
updates[field] = updateData[field];
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(updates).length > 0) {
|
||||
updates.version = user.version + 1;
|
||||
updates.last_change = new Date();
|
||||
|
||||
await user.update(updates);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Benutzer mit allen Beziehungen abrufen
|
||||
* @param {number} id - Benutzer-ID
|
||||
* @returns {Promise<Object|null>} Benutzer mit Beziehungen
|
||||
*/
|
||||
async findByIdWithRelations(id) {
|
||||
const { User, State, AuthInfo, WeeklyWorktime, Vacation, Sick } = database.getModels();
|
||||
|
||||
return await User.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: State,
|
||||
as: 'state',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
model: AuthInfo,
|
||||
as: 'authInfo',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
model: WeeklyWorktime,
|
||||
as: 'weeklyWorktimes',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
model: Vacation,
|
||||
as: 'vacations',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
model: Sick,
|
||||
as: 'sickLeaves',
|
||||
required: false
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new UserRepository();
|
||||
564
backend/src/repositories/WorklogRepository.js
Normal file
564
backend/src/repositories/WorklogRepository.js
Normal file
@@ -0,0 +1,564 @@
|
||||
const database = require('../config/database');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* Repository für Worklog-Datenbankzugriff
|
||||
* Verwendet Sequelize ORM
|
||||
*/
|
||||
class WorklogRepository {
|
||||
/**
|
||||
* Alle Worklog-Einträge für einen Benutzer abrufen
|
||||
* @param {number} userId - ID des Benutzers
|
||||
* @param {Object} options - Filteroptionen
|
||||
* @returns {Promise<Array>} Liste der Worklog-Einträge
|
||||
*/
|
||||
async findAllByUser(userId, options = {}) {
|
||||
const { Worklog } = database.getModels();
|
||||
const { limit, offset, orderBy = 'tstamp', order = 'DESC' } = options;
|
||||
|
||||
return await Worklog.findAll({
|
||||
where: { user_id: userId },
|
||||
order: [[orderBy, order]],
|
||||
limit: limit || null,
|
||||
offset: offset || 0
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Worklog-Eintrag anhand der ID abrufen
|
||||
* @param {number} id - Worklog-ID
|
||||
* @returns {Promise<Object|null>} Worklog-Eintrag oder null
|
||||
*/
|
||||
async findById(id) {
|
||||
const { Worklog } = database.getModels();
|
||||
|
||||
return await Worklog.findByPk(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Neuen Worklog-Eintrag erstellen
|
||||
* @param {Object} worklogData - Worklog-Daten
|
||||
* @returns {Promise<Object>} Erstellter Worklog-Eintrag
|
||||
*/
|
||||
async create(worklogData) {
|
||||
const { Worklog } = database.getModels();
|
||||
|
||||
const { user_id, state, tstamp, relatedTo_id = null } = worklogData;
|
||||
|
||||
return await Worklog.create({
|
||||
version: 0,
|
||||
user_id,
|
||||
state,
|
||||
tstamp: tstamp || new Date(),
|
||||
relatedTo_id
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Worklog-Eintrag aktualisieren
|
||||
* @param {number} id - Worklog-ID
|
||||
* @param {Object} updateData - Zu aktualisierende Daten
|
||||
* @returns {Promise<Object>} Aktualisierter Worklog-Eintrag
|
||||
*/
|
||||
async update(id, updateData) {
|
||||
const { Worklog } = database.getModels();
|
||||
|
||||
const worklog = await Worklog.findByPk(id);
|
||||
if (!worklog) {
|
||||
throw new Error(`Worklog mit ID ${id} nicht gefunden`);
|
||||
}
|
||||
|
||||
// Update-Daten vorbereiten
|
||||
const updates = {};
|
||||
|
||||
if (updateData.state !== undefined) {
|
||||
updates.state = updateData.state;
|
||||
}
|
||||
if (updateData.tstamp !== undefined) {
|
||||
updates.tstamp = updateData.tstamp;
|
||||
}
|
||||
if (updateData.relatedTo_id !== undefined) {
|
||||
updates.relatedTo_id = updateData.relatedTo_id;
|
||||
}
|
||||
|
||||
// Version inkrementieren
|
||||
updates.version = worklog.version + 1;
|
||||
|
||||
await worklog.update(updates);
|
||||
return worklog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Worklog-Eintrag löschen
|
||||
* @param {number} id - Worklog-ID
|
||||
* @returns {Promise<boolean>} true wenn erfolgreich
|
||||
*/
|
||||
async delete(id) {
|
||||
const { Worklog } = database.getModels();
|
||||
|
||||
const deleted = await Worklog.destroy({
|
||||
where: { id }
|
||||
});
|
||||
|
||||
return deleted > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Letzten Worklog-Eintrag für Benutzer finden
|
||||
* @param {number} userId - Benutzer-ID
|
||||
* @returns {Promise<Object|null>} Letzter Worklog-Eintrag oder null
|
||||
*/
|
||||
async findLatestByUser(userId) {
|
||||
const { Worklog } = database.getModels();
|
||||
|
||||
return await Worklog.findOne({
|
||||
where: { user_id: userId },
|
||||
order: [['tstamp', 'DESC'], ['id', 'DESC']],
|
||||
raw: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Alle Worklog-Einträge für Benutzer finden
|
||||
* @param {number} userId - Benutzer-ID
|
||||
* @returns {Promise<Array>} Array von Worklog-Einträgen
|
||||
*/
|
||||
async findByUser(userId) {
|
||||
const { Worklog } = database.getModels();
|
||||
|
||||
return await Worklog.findAll({
|
||||
where: { user_id: userId },
|
||||
order: [['tstamp', 'ASC'], ['id', 'ASC']],
|
||||
raw: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Letzten offenen Worklog-Eintrag für Benutzer finden
|
||||
* @param {number} userId - Benutzer-ID
|
||||
* @returns {Promise<Object|null>} Laufender Worklog-Eintrag oder null
|
||||
*/
|
||||
async findRunningByUser(userId) {
|
||||
const { Worklog } = database.getModels();
|
||||
|
||||
return await Worklog.findOne({
|
||||
where: {
|
||||
user_id: userId,
|
||||
relatedTo_id: null
|
||||
},
|
||||
order: [['tstamp', 'DESC']],
|
||||
include: [{
|
||||
model: Worklog,
|
||||
as: 'clockOut',
|
||||
required: false
|
||||
}]
|
||||
}).then(worklog => {
|
||||
// Nur zurückgeben wenn kein clockOut existiert
|
||||
if (worklog && !worklog.clockOut) {
|
||||
return worklog;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Vacation-Einträge für einen Benutzer in einem Datumsbereich abrufen
|
||||
* @param {number} userId - Benutzer-ID
|
||||
* @param {Date} startDate - Start-Datum
|
||||
* @param {Date} endDate - End-Datum
|
||||
* @returns {Promise<Array>} Array von Vacation-Einträgen mit expandierten Tagen
|
||||
*/
|
||||
async getVacationsByUserInDateRange(userId, startDate, endDate) {
|
||||
const { Vacation } = database.getModels();
|
||||
|
||||
try {
|
||||
// Hole alle Vacation-Einträge, die sich mit dem Datumsbereich überschneiden
|
||||
const vacations = await Vacation.findAll({
|
||||
where: {
|
||||
user_id: userId,
|
||||
[Op.or]: [
|
||||
{
|
||||
first_day: {
|
||||
[Op.between]: [startDate, endDate]
|
||||
}
|
||||
},
|
||||
{
|
||||
last_day: {
|
||||
[Op.between]: [startDate, endDate]
|
||||
}
|
||||
},
|
||||
{
|
||||
[Op.and]: [
|
||||
{ first_day: { [Op.lte]: startDate } },
|
||||
{ last_day: { [Op.gte]: endDate } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
raw: true,
|
||||
order: [['first_day', 'ASC']]
|
||||
});
|
||||
|
||||
// Expandiere jeden Vacation-Eintrag in einzelne Tage
|
||||
const expandedVacations = [];
|
||||
|
||||
vacations.forEach(vac => {
|
||||
const first = new Date(vac.first_day);
|
||||
const last = new Date(vac.last_day);
|
||||
|
||||
// Iteriere über alle Tage im Urlaubsbereich
|
||||
for (let d = new Date(first); d <= last; d.setDate(d.getDate() + 1)) {
|
||||
// Nur Tage hinzufügen, die im gewünschten Bereich liegen
|
||||
if (d >= startDate && d <= endDate) {
|
||||
// Überspringe Wochenenden (Samstag=6, Sonntag=0)
|
||||
const dayOfWeek = d.getDay();
|
||||
if (dayOfWeek !== 0 && dayOfWeek !== 6) {
|
||||
expandedVacations.push({
|
||||
date: new Date(d),
|
||||
half_day: vac.vacation_type === 1 ? 1 : 0,
|
||||
vacation_type: vac.vacation_type
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return expandedVacations;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Vacation-Einträge:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sick-Einträge für einen Benutzer in einem Datumsbereich abrufen
|
||||
* @param {number} userId - Benutzer-ID
|
||||
* @param {Date} startDate - Start-Datum
|
||||
* @param {Date} endDate - End-Datum
|
||||
* @returns {Promise<Array>} Array von Sick-Einträgen mit expandierten Tagen
|
||||
*/
|
||||
async getSickByUserInDateRange(userId, startDate, endDate) {
|
||||
const { Sick, SickType } = database.getModels();
|
||||
|
||||
try {
|
||||
// Hole alle Sick-Einträge, die sich mit dem Datumsbereich überschneiden
|
||||
const sickEntries = await Sick.findAll({
|
||||
where: {
|
||||
user_id: userId,
|
||||
[Op.or]: [
|
||||
{
|
||||
first_day: {
|
||||
[Op.between]: [startDate, endDate]
|
||||
}
|
||||
},
|
||||
{
|
||||
last_day: {
|
||||
[Op.between]: [startDate, endDate]
|
||||
}
|
||||
},
|
||||
{
|
||||
[Op.and]: [
|
||||
{ first_day: { [Op.lte]: startDate } },
|
||||
{ last_day: { [Op.gte]: endDate } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
include: [{
|
||||
model: SickType,
|
||||
as: 'sickType',
|
||||
attributes: ['description']
|
||||
}],
|
||||
order: [['first_day', 'ASC']]
|
||||
});
|
||||
|
||||
// Expandiere jeden Sick-Eintrag in einzelne Tage
|
||||
const expandedSick = [];
|
||||
|
||||
sickEntries.forEach(sick => {
|
||||
const first = new Date(sick.first_day);
|
||||
const last = new Date(sick.last_day);
|
||||
const sickTypeDesc = sick.sickType?.description || 'self';
|
||||
|
||||
// Iteriere über alle Tage im Krankheitsbereich
|
||||
for (let d = new Date(first); d <= last; d.setDate(d.getDate() + 1)) {
|
||||
// Nur Tage hinzufügen, die im gewünschten Bereich liegen
|
||||
if (d >= startDate && d <= endDate) {
|
||||
// Überspringe Wochenenden (Samstag=6, Sonntag=0)
|
||||
const dayOfWeek = d.getDay();
|
||||
if (dayOfWeek !== 0 && dayOfWeek !== 6) {
|
||||
expandedSick.push({
|
||||
date: new Date(d),
|
||||
sick_type: sickTypeDesc,
|
||||
sick_type_id: sick.sick_type_id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return expandedSick;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Sick-Einträge:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Timefix-Einträge für Worklog-IDs abrufen
|
||||
* @param {Array<number>} worklogIds - Array von Worklog-IDs
|
||||
* @returns {Promise<Map>} Map von worklog_id zu Timefix-Einträgen
|
||||
*/
|
||||
async getTimefixesByWorklogIds(worklogIds) {
|
||||
if (!worklogIds || worklogIds.length === 0) {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
const { Timefix } = database.getModels();
|
||||
|
||||
try {
|
||||
const timefixes = await Timefix.findAll({
|
||||
where: {
|
||||
worklog_id: {
|
||||
[Op.in]: worklogIds
|
||||
}
|
||||
},
|
||||
raw: true
|
||||
});
|
||||
|
||||
// Gruppiere nach worklog_id
|
||||
const timefixMap = new Map();
|
||||
timefixes.forEach(fix => {
|
||||
if (!timefixMap.has(fix.worklog_id)) {
|
||||
timefixMap.set(fix.worklog_id, []);
|
||||
}
|
||||
timefixMap.get(fix.worklog_id).push(fix);
|
||||
});
|
||||
|
||||
return timefixMap;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Timefix-Einträge:', error);
|
||||
return new Map();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Worklog-Paare für Benutzer in einem Datumsbereich finden
|
||||
* @param {number} userId - Benutzer-ID
|
||||
* @param {Date} startDate - Start-Datum
|
||||
* @param {Date} endDate - End-Datum
|
||||
* @returns {Promise<Array>} Array von Worklog-Paaren
|
||||
*/
|
||||
async findPairsByUserInDateRange(userId, startDate, endDate) {
|
||||
const { Worklog } = database.getModels();
|
||||
|
||||
try {
|
||||
const results = await Worklog.findAll({
|
||||
attributes: ['id', 'version', 'user_id', 'state', 'tstamp', 'relatedTo_id'],
|
||||
where: {
|
||||
user_id: userId,
|
||||
tstamp: {
|
||||
[Op.between]: [startDate, endDate]
|
||||
}
|
||||
},
|
||||
order: [['tstamp', 'ASC']],
|
||||
raw: true
|
||||
});
|
||||
|
||||
// Gruppiere Start/Stop-Paare basierend auf dem action-Feld
|
||||
const pairs = [];
|
||||
const startEntries = {};
|
||||
|
||||
results.forEach(entry => {
|
||||
// Parse state JSON
|
||||
let action = '';
|
||||
try {
|
||||
const state = typeof entry.state === 'string' ? JSON.parse(entry.state) : entry.state;
|
||||
action = state.action || state;
|
||||
} catch (e) {
|
||||
action = entry.state;
|
||||
}
|
||||
|
||||
if (action === 'start work') {
|
||||
startEntries[entry.id] = entry;
|
||||
} else if (action === 'stop work' && entry.relatedTo_id) {
|
||||
const startEntry = startEntries[entry.relatedTo_id];
|
||||
if (startEntry) {
|
||||
pairs.push({
|
||||
id: startEntry.id,
|
||||
start_time: startEntry.tstamp,
|
||||
end_time: entry.tstamp,
|
||||
start_state: startEntry.state,
|
||||
end_state: entry.state
|
||||
});
|
||||
delete startEntries[entry.relatedTo_id];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Füge laufende Einträge hinzu
|
||||
Object.values(startEntries).forEach(startEntry => {
|
||||
pairs.push({
|
||||
id: startEntry.id,
|
||||
start_time: startEntry.tstamp,
|
||||
end_time: null,
|
||||
start_state: startEntry.state,
|
||||
end_state: null
|
||||
});
|
||||
});
|
||||
|
||||
return pairs;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Worklog-Paare:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Worklog-Einträge nach Datumsbereich abrufen
|
||||
* @param {number} userId - Benutzer-ID
|
||||
* @param {Date} startDate - Startdatum
|
||||
* @param {Date} endDate - Enddatum
|
||||
* @returns {Promise<Array>} Gefilterte Worklog-Einträge
|
||||
*/
|
||||
async findByDateRange(userId, startDate, endDate) {
|
||||
const { Worklog } = database.getModels();
|
||||
|
||||
return await Worklog.findAll({
|
||||
where: {
|
||||
user_id: userId,
|
||||
tstamp: {
|
||||
[Op.between]: [startDate, endDate]
|
||||
}
|
||||
},
|
||||
order: [['tstamp', 'ASC']] // Aufsteigend, damit Start vor Stop kommt
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Zusammengehörige Worklog-Paare abrufen (Clock In/Out)
|
||||
* @param {number} userId - Benutzer-ID
|
||||
* @param {Object} options - Optionen
|
||||
* @returns {Promise<Array>} Liste von Worklog-Paaren
|
||||
*/
|
||||
async findPairsByUser(userId, options = {}) {
|
||||
const { Worklog } = database.getModels();
|
||||
const sequelize = database.getSequelize();
|
||||
const { limit, offset } = options;
|
||||
|
||||
// Raw Query für bessere Performance bei Pairs
|
||||
const query = `
|
||||
SELECT
|
||||
w1.id as start_id,
|
||||
w1.tstamp as start_time,
|
||||
w1.state as start_state,
|
||||
w2.id as end_id,
|
||||
w2.tstamp as end_time,
|
||||
w2.state as end_state,
|
||||
TIMESTAMPDIFF(SECOND, w1.tstamp, w2.tstamp) as duration
|
||||
FROM worklog w1
|
||||
LEFT JOIN worklog w2 ON w2.relatedTo_id = w1.id
|
||||
WHERE w1.user_id = :userId
|
||||
AND w1.relatedTo_id IS NULL
|
||||
ORDER BY w1.tstamp DESC
|
||||
${limit ? `LIMIT ${limit}` : ''}
|
||||
${offset ? `OFFSET ${offset}` : ''}
|
||||
`;
|
||||
|
||||
const results = await sequelize.query(query, {
|
||||
replacements: { userId },
|
||||
type: sequelize.constructor.QueryTypes.SELECT
|
||||
});
|
||||
|
||||
return Array.isArray(results) ? results : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistiken für Benutzer berechnen
|
||||
* @param {number} userId - Benutzer-ID
|
||||
* @returns {Promise<Object>} Statistik-Objekt
|
||||
*/
|
||||
async getStatistics(userId) {
|
||||
const sequelize = database.getSequelize();
|
||||
|
||||
const query = `
|
||||
SELECT
|
||||
COUNT(DISTINCT w1.id) as total_entries,
|
||||
COUNT(DISTINCT w2.id) as completed_entries,
|
||||
COUNT(DISTINCT CASE WHEN w2.id IS NULL THEN w1.id END) as running_entries,
|
||||
COALESCE(SUM(TIMESTAMPDIFF(SECOND, w1.tstamp, w2.tstamp)), 0) as total_seconds
|
||||
FROM worklog w1
|
||||
LEFT JOIN worklog w2 ON w2.relatedTo_id = w1.id
|
||||
WHERE w1.user_id = :userId
|
||||
AND w1.relatedTo_id IS NULL
|
||||
`;
|
||||
|
||||
const results = await sequelize.query(query, {
|
||||
replacements: { userId },
|
||||
type: sequelize.constructor.QueryTypes.SELECT
|
||||
});
|
||||
|
||||
return (Array.isArray(results) && results[0]) ? results[0] : {
|
||||
total_entries: 0,
|
||||
completed_entries: 0,
|
||||
running_entries: 0,
|
||||
total_seconds: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistiken für heute abrufen
|
||||
* @param {number} userId - Benutzer-ID
|
||||
* @returns {Promise<Object>} Heutige Statistiken
|
||||
*/
|
||||
async getTodayStatistics(userId) {
|
||||
const sequelize = database.getSequelize();
|
||||
|
||||
const query = `
|
||||
SELECT
|
||||
COUNT(DISTINCT w1.id) as entries,
|
||||
COALESCE(SUM(TIMESTAMPDIFF(SECOND, w1.tstamp, w2.tstamp)), 0) as seconds
|
||||
FROM worklog w1
|
||||
LEFT JOIN worklog w2 ON w2.relatedTo_id = w1.id
|
||||
WHERE w1.user_id = :userId
|
||||
AND w1.relatedTo_id IS NULL
|
||||
AND DATE(w1.tstamp) = CURDATE()
|
||||
`;
|
||||
|
||||
const results = await sequelize.query(query, {
|
||||
replacements: { userId },
|
||||
type: sequelize.constructor.QueryTypes.SELECT
|
||||
});
|
||||
|
||||
return (Array.isArray(results) && results[0]) ? results[0] : { entries: 0, seconds: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* Feiertage in einem Datumsbereich abrufen
|
||||
* @param {Date} startDate - Start-Datum
|
||||
* @param {Date} endDate - End-Datum
|
||||
* @returns {Promise<Array>} Array von Holiday-Einträgen mit Datum und Stunden
|
||||
*/
|
||||
async getHolidaysInDateRange(startDate, endDate) {
|
||||
const { Holiday } = database.getModels();
|
||||
|
||||
try {
|
||||
const holidays = await Holiday.findAll({
|
||||
where: {
|
||||
date: {
|
||||
[Op.between]: [startDate, endDate]
|
||||
}
|
||||
},
|
||||
raw: true,
|
||||
order: [['date', 'ASC']]
|
||||
});
|
||||
|
||||
return holidays;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Feiertage:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new WorklogRepository();
|
||||
Reference in New Issue
Block a user