Files
timetracker/Data/TimetrackerService.cs
T
2026-06-07 23:36:45 +02:00

199 lines
8.0 KiB
C#

using Microsoft.EntityFrameworkCore;
namespace timetracker.Data;
public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
{
public async Task<List<WorkDay>> GetWeekAsync(int userId, DateOnly monday)
{
await using var db = await factory.CreateDbContextAsync();
return await db.WorkDays
.Include(w => w.Breaks)
.Where(w => w.UserId == userId && w.Date >= monday && w.Date < monday.AddDays(7))
.OrderBy(w => w.Date)
.ToListAsync();
}
public async Task UpsertWorkDayAsync(WorkDay workDay)
{
await using var db = await factory.CreateDbContextAsync();
var existing = await db.WorkDays
.Include(w => w.Breaks)
.FirstOrDefaultAsync(w => w.UserId == workDay.UserId && w.Date == workDay.Date);
if (existing == null)
{
workDay.Id = 0;
foreach (var b in workDay.Breaks) b.Id = 0;
db.WorkDays.Add(workDay);
}
else
{
existing.StartTime = workDay.StartTime;
existing.EndTime = workDay.EndTime;
db.BreakEntries.RemoveRange(existing.Breaks);
existing.Breaks.Clear();
foreach (var b in workDay.Breaks)
existing.Breaks.Add(new BreakEntry
{
WorkDayId = existing.Id,
StartTime = b.StartTime,
EndTime = b.EndTime
});
}
await db.SaveChangesAsync();
}
public async Task<AppSettings> GetSettingsAsync(int userId)
{
await using var db = await factory.CreateDbContextAsync();
return await db.AppSettings.FirstOrDefaultAsync(s => s.UserId == userId)
?? new AppSettings { UserId = userId };
}
public async Task SaveSettingsAsync(AppSettings settings)
{
await using var db = await factory.CreateDbContextAsync();
var existing = await db.AppSettings.FirstOrDefaultAsync(s => s.UserId == settings.UserId);
if (existing == null)
db.AppSettings.Add(settings);
else
{
existing.DailyTargetHours = settings.DailyTargetHours;
existing.MinimumBreakMinutes = settings.MinimumBreakMinutes;
existing.VacationDaysPerYear = settings.VacationDaysPerYear;
existing.WorkMonday = settings.WorkMonday;
existing.WorkTuesday = settings.WorkTuesday;
existing.WorkWednesday = settings.WorkWednesday;
existing.WorkThursday = settings.WorkThursday;
existing.WorkFriday = settings.WorkFriday;
existing.WorkSaturday = settings.WorkSaturday;
existing.WorkSunday = settings.WorkSunday;
}
await db.SaveChangesAsync();
}
// ── Urlaub ────────────────────────────────────────────────────────────
public async Task<List<VacationDay>> GetVacationDaysAsync(int userId, int year)
{
await using var db = await factory.CreateDbContextAsync();
return await db.VacationDays
.Where(v => v.UserId == userId && v.Date.Year == year)
.OrderBy(v => v.Date)
.ToListAsync();
}
public async Task AddVacationDayAsync(VacationDay vacationDay)
{
await using var db = await factory.CreateDbContextAsync();
var exists = await db.VacationDays.AnyAsync(v => v.UserId == vacationDay.UserId && v.Date == vacationDay.Date);
if (!exists)
{
vacationDay.Id = 0;
db.VacationDays.Add(vacationDay);
await db.SaveChangesAsync();
}
}
public async Task RemoveVacationDayAsync(int userId, int id)
{
await using var db = await factory.CreateDbContextAsync();
var v = await db.VacationDays.FirstOrDefaultAsync(v => v.Id == id && v.UserId == userId);
if (v != null)
{
db.VacationDays.Remove(v);
await db.SaveChangesAsync();
}
}
// ── Gleitzeitkonto ───────────────────────────────────────────────────
public async Task<TimeSpan> GetTotalOvertimeAsync(int userId, AppSettings settings)
{
await using var db = await factory.CreateDbContextAsync();
// 1. Finde das Startdatum für die Berechnung
var firstDay = await db.WorkDays
.Where(w => w.UserId == userId)
.OrderBy(w => w.Date)
.Select(w => (DateOnly?)w.Date)
.FirstOrDefaultAsync();
if (firstDay == null && !settings.FlexTimeStartDate.HasValue)
return TimeSpan.FromHours(settings.FlexTimeStartingBalanceHours);
var startDate = settings.FlexTimeStartDate ?? firstDay!.Value;
var endDate = DateOnly.FromDateTime(DateTime.Today);
if (startDate > endDate)
return TimeSpan.FromHours(settings.FlexTimeStartingBalanceHours);
// 2. Lade alle erfassten Arbeitstage in diesem Zeitraum
var workDays = await db.WorkDays
.Include(w => w.Breaks)
.Where(w => w.UserId == userId && w.Date >= startDate && w.Date <= endDate)
.ToDictionaryAsync(w => w.Date);
// 3. Lade alle Feiertage in diesem Zeitraum
var holidaysList = await db.PublicHolidays
.Where(h => h.Date >= startDate && h.Date <= endDate)
.ToListAsync();
var state = settings.GermanState;
var holidaySet = holidaysList
.Where(h => string.IsNullOrEmpty(h.Counties) || (!string.IsNullOrEmpty(state) && h.Counties.Split(',').Contains(state)))
.Select(h => h.Date)
.ToHashSet();
// 4. Lade alle Urlaubstage in diesem Zeitraum
var vacationSet = await db.VacationDays
.Where(v => v.UserId == userId && v.Date >= startDate && v.Date <= endDate)
.Select(v => v.Date)
.ToHashSetAsync();
double totalOvertimeHours = settings.FlexTimeStartingBalanceHours;
for (var date = startDate; date <= endDate; date = date.AddDays(1))
{
bool isWorkDay = settings.IsWorkDay(date.DayOfWeek);
bool isHoliday = holidaySet.Contains(date);
bool isVacation = vacationSet.Contains(date);
// Sollzeit gilt nur an regulären Arbeitstagen, die weder Feiertag noch Urlaub sind
double target = (isWorkDay && !isHoliday && !isVacation) ? settings.DailyTargetHours : 0.0;
double actual = 0.0;
if (workDays.TryGetValue(date, out var wd) && wd.StartTime != null && wd.EndTime != null)
{
var gross = wd.EndTime.Value.ToTimeSpan() - wd.StartTime.Value.ToTimeSpan();
if (gross > TimeSpan.Zero)
{
var breakTotal = wd.Breaks
.Where(b => b.StartTime.HasValue && b.EndTime.HasValue && b.EndTime > b.StartTime)
.Aggregate(TimeSpan.Zero, (s, b) =>
s + (b.EndTime!.Value.ToTimeSpan() - b.StartTime!.Value.ToTimeSpan()));
actual = (gross - breakTotal).TotalHours;
if (actual < 0) actual = 0.0;
}
}
totalOvertimeHours += (actual - target);
}
return TimeSpan.FromHours(totalOvertimeHours);
}
// ── Monatsübersicht ───────────────────────────────────────────────────
public async Task<List<WorkDay>> GetMonthAsync(int userId, int year, int month)
{
await using var db = await factory.CreateDbContextAsync();
var from = new DateOnly(year, month, 1);
var to = from.AddMonths(1);
return await db.WorkDays
.Include(w => w.Breaks)
.Where(w => w.UserId == userId && w.Date >= from && w.Date < to)
.OrderBy(w => w.Date)
.ToListAsync();
}
}