This commit is contained in:
MarcWieland
2026-06-26 23:17:08 +02:00
parent 4a69095f30
commit 62e58fc9ad
8 changed files with 625 additions and 228 deletions
+3
View File
@@ -0,0 +1,3 @@
using System;
using PdfSharp.Fonts;
class Program { static void Main() { Console.WriteLine("Hello"); } }
Binary file not shown.
Binary file not shown.
+43 -43
View File
@@ -162,8 +162,6 @@ using (var scope = app.Services.CreateScope())
{ {
var factory = scope.ServiceProvider.GetRequiredService<IDbContextFactory<TimetrackerDbContext>>(); var factory = scope.ServiceProvider.GetRequiredService<IDbContextFactory<TimetrackerDbContext>>();
await using var db = await factory.CreateDbContextAsync(); await using var db = await factory.CreateDbContextAsync();
var databaseCreatedFromModel = false;
if (dbProvider.Equals("PostgreSQL", StringComparison.OrdinalIgnoreCase)) if (dbProvider.Equals("PostgreSQL", StringComparison.OrdinalIgnoreCase))
{ {
try try
@@ -174,24 +172,6 @@ using (var scope = app.Services.CreateScope())
await conn.OpenAsync(); await conn.OpenAsync();
} }
using (var cmdCheckTables = conn.CreateCommand())
{
cmdCheckTables.CommandText = @"
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name <> '__EFMigrationsHistory';";
var existingTableCount = Convert.ToInt32(await cmdCheckTables.ExecuteScalarAsync() ?? 0);
if (existingTableCount == 0)
{
await db.Database.EnsureCreatedAsync();
databaseCreatedFromModel = true;
}
}
if (!databaseCreatedFromModel)
{
// Check if AppSettings table already exists // Check if AppSettings table already exists
using (var cmdCheck = conn.CreateCommand()) using (var cmdCheck = conn.CreateCommand())
{ {
@@ -212,28 +192,51 @@ using (var scope = app.Services.CreateScope())
await cmdCreateHistory.ExecuteNonQueryAsync(); await cmdCreateHistory.ExecuteNonQueryAsync();
} }
// Check if the initial migration is already recorded // Seed the history table conditionally for each migration based on actual schema presence
using (var cmdCheckHistory = conn.CreateCommand()) using (var cmdSyncHistory = conn.CreateCommand())
{ {
cmdCheckHistory.CommandText = @"SELECT COUNT(*) FROM ""__EFMigrationsHistory"" WHERE ""MigrationId"" = '20260520133634_Initial';"; cmdSyncHistory.CommandText = @"
var historyCount = Convert.ToInt32(await cmdCheckHistory.ExecuteScalarAsync() ?? 0); INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"")
SELECT '20260520133634_Initial', '10.0.9'
WHERE NOT EXISTS (SELECT 1 FROM ""__EFMigrationsHistory"" WHERE ""MigrationId"" = '20260520133634_Initial');
if (historyCount == 0) INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"")
SELECT '20260520200000_AddPublicHolidays', '10.0.9'
WHERE NOT EXISTS (SELECT 1 FROM ""__EFMigrationsHistory"" WHERE ""MigrationId"" = '20260520200000_AddPublicHolidays');
INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"")
SELECT '20260522081459_AddMultiUser', '10.0.9'
WHERE NOT EXISTS (SELECT 1 FROM ""__EFMigrationsHistory"" WHERE ""MigrationId"" = '20260522081459_AddMultiUser');
INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"")
SELECT '20260607213215_AddFlexTimeAndHolidayState', '10.0.9'
WHERE NOT EXISTS (SELECT 1 FROM ""__EFMigrationsHistory"" WHERE ""MigrationId"" = '20260607213215_AddFlexTimeAndHolidayState');
-- Seed AddMultiTenancy only if Tenants table exists
INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"")
SELECT '20260624204954_AddMultiTenancy', '10.0.9'
WHERE EXISTS (SELECT FROM pg_tables WHERE schemaname = 'public' AND tablename = 'Tenants')
AND NOT EXISTS (SELECT 1 FROM ""__EFMigrationsHistory"" WHERE ""MigrationId"" = '20260624204954_AddMultiTenancy');
-- Seed AddTenantBranding only if TenantColor column exists
INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"")
SELECT '20260624210436_AddTenantBranding', '10.0.9'
WHERE EXISTS (SELECT FROM information_schema.columns WHERE table_name = 'Tenants' AND column_name = 'TenantColor')
AND NOT EXISTS (SELECT 1 FROM ""__EFMigrationsHistory"" WHERE ""MigrationId"" = '20260624210436_AddTenantBranding');
-- Seed PendingChanges only if IsApproved column in VacationDays exists
INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"")
SELECT '20260625184756_PendingChanges', '10.0.9'
WHERE EXISTS (SELECT FROM information_schema.columns WHERE table_name = 'VacationDays' AND column_name = 'IsApproved')
AND NOT EXISTS (SELECT 1 FROM ""__EFMigrationsHistory"" WHERE ""MigrationId"" = '20260625184756_PendingChanges');
";
await cmdSyncHistory.ExecuteNonQueryAsync();
}
}
else
{ {
// Seed the history table with the migrations that were already created by EnsureCreated in older versions // DB is empty, MigrateAsync will create everything. We don't need to patch types later if it was purely MigrateAsync...
using (var cmdInsertHistory = conn.CreateCommand()) // Wait, SQLite migrations might still create broken defaults on MigrateAsync. We keep EnsurePostgresIntegerPrimaryKeyDefaultAsync below.
{
cmdInsertHistory.CommandText = @"
INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"") VALUES
('20260520133634_Initial', '10.0.9'),
('20260520200000_AddPublicHolidays', '10.0.9'),
('20260522081459_AddMultiUser', '10.0.9'),
('20260607213215_AddFlexTimeAndHolidayState', '10.0.9');";
await cmdInsertHistory.ExecuteNonQueryAsync();
}
}
}
}
} }
} }
} }
@@ -243,12 +246,9 @@ using (var scope = app.Services.CreateScope())
} }
} }
if (!databaseCreatedFromModel)
{
await db.Database.MigrateAsync(); await db.Database.MigrateAsync();
}
if (dbProvider.Equals("PostgreSQL", StringComparison.OrdinalIgnoreCase) && !databaseCreatedFromModel) if (dbProvider.Equals("PostgreSQL", StringComparison.OrdinalIgnoreCase))
{ {
try try
{ {
@@ -0,0 +1,41 @@
using System;
using System.IO;
using System.Reflection;
using PdfSharp.Fonts;
namespace timetracker.Server.Services
{
public class PdfFontResolver : IFontResolver
{
public byte[] GetFont(string faceName)
{
var assembly = Assembly.GetExecutingAssembly();
// Map faceName to Embedded Resource stream
string resourceName = faceName.ToLower() switch
{
"opensans-bold" => "timetracker.Server.Fonts.OpenSans-Bold.ttf",
_ => "timetracker.Server.Fonts.OpenSans-Regular.ttf"
};
using var stream = assembly.GetManifestResourceStream(resourceName);
if (stream == null)
{
throw new InvalidOperationException($"Could not load font resource '{resourceName}'");
}
using var ms = new MemoryStream();
stream.CopyTo(ms);
return ms.ToArray();
}
public FontResolverInfo ResolveTypeface(string familyName, bool isBold, bool isItalic)
{
if (isBold)
{
return new FontResolverInfo("OpenSans-Bold");
}
return new FontResolverInfo("OpenSans-Regular");
}
}
}
+150 -121
View File
@@ -1,6 +1,6 @@
using QuestPDF.Fluent; using MigraDoc.DocumentObjectModel;
using QuestPDF.Helpers; using MigraDoc.DocumentObjectModel.Tables;
using QuestPDF.Infrastructure; using MigraDoc.Rendering;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -10,12 +10,6 @@ namespace timetracker.Server.Services
{ {
public class PdfGeneratorService public class PdfGeneratorService
{ {
public PdfGeneratorService()
{
// Configure QuestPDF community license
QuestPDF.Settings.License = LicenseType.Community;
}
public byte[] GenerateTimesheetPdf( public byte[] GenerateTimesheetPdf(
User user, User user,
Tenant? tenant, Tenant? tenant,
@@ -26,57 +20,76 @@ namespace timetracker.Server.Services
List<VacationDay> vacationDays, List<VacationDay> vacationDays,
List<PublicHoliday> holidays) List<PublicHoliday> holidays)
{ {
var document = Document.Create(container => if (PdfSharp.Fonts.GlobalFontSettings.FontResolver == null)
{ {
container.Page(page => PdfSharp.Fonts.GlobalFontSettings.FontResolver = new PdfFontResolver();
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x.FontSize(10).FontFamily(Fonts.Arial));
page.Header().Element(c => ComposeHeader(c, user, tenant, year, month));
page.Content().Element(c => ComposeContent(c, user, settings, year, month, workDays, vacationDays, holidays));
page.Footer().Element(ComposeFooter);
});
});
return document.GeneratePdf();
} }
private void ComposeHeader(IContainer container, User user, Tenant? tenant, int year, int month) var document = new Document();
document.Info.Title = "Stundenzettel";
document.Info.Author = tenant?.Name ?? "Timetracker";
var section = document.AddSection();
section.PageSetup.PageFormat = PageFormat.A4;
section.PageSetup.TopMargin = "2cm";
section.PageSetup.BottomMargin = "2cm";
section.PageSetup.LeftMargin = "2cm";
section.PageSetup.RightMargin = "2cm";
var style = document.Styles["Normal"];
if (style != null && style.Font != null)
{
style.Font.Name = "OpenSans";
style.Font.Size = 10;
}
ComposeHeader(section, user, tenant, year, month);
ComposeContent(section, document, user, settings, year, month, workDays, vacationDays, holidays);
ComposeFooter(section);
var pdfRenderer = new PdfDocumentRenderer()
{
Document = document
};
pdfRenderer.RenderDocument();
using var stream = new System.IO.MemoryStream();
pdfRenderer.PdfDocument.Save(stream, false);
return stream.ToArray();
}
private void ComposeHeader(Section section, User user, Tenant? tenant, int year, int month)
{ {
var monthName = new DateTime(year, month, 1).ToString("MMMM yyyy", new System.Globalization.CultureInfo("de-DE")); var monthName = new DateTime(year, month, 1).ToString("MMMM yyyy", new System.Globalization.CultureInfo("de-DE"));
var companyName = tenant?.Name ?? "Hauptdomain"; var companyName = tenant?.Name ?? "Hauptdomain";
container.Row(row => var paragraph = section.AddParagraph($"Stundenzettel - {monthName}");
{ paragraph.Format.Font.Size = 20;
row.RelativeItem().Column(column => paragraph.Format.Font.Bold = true;
{ paragraph.Format.Font.Color = Colors.DarkBlue;
column.Item().Text($"Stundenzettel - {monthName}").FontSize(20).SemiBold().FontColor(Colors.Blue.Darken2); paragraph.Format.SpaceAfter = "0.2cm";
column.Item().Text($"Mitarbeiter: {user.Username}").FontSize(14).SemiBold();
column.Item().PaddingTop(2).Text($"Firma: {companyName}").FontSize(11).FontColor(Colors.Grey.Darken2); paragraph = section.AddParagraph($"Mitarbeiter: {user.Username}");
}); paragraph.Format.Font.Size = 14;
}); paragraph.Format.Font.Bold = true;
paragraph.Format.SpaceAfter = "0.1cm";
paragraph = section.AddParagraph($"Firma: {companyName}");
paragraph.Format.Font.Size = 11;
paragraph.Format.Font.Color = Colors.DarkGray;
paragraph.Format.SpaceAfter = "1cm";
} }
private void ComposeContent(IContainer container, User user, AppSettings settings, int year, int month, List<WorkDay> workDays, List<VacationDay> vacationDays, List<PublicHoliday> holidays) private void ComposeContent(Section section, Document document, User user, AppSettings settings, int year, int month, List<WorkDay> workDays, List<VacationDay> vacationDays, List<PublicHoliday> holidays)
{ {
TimeSpan totalNet = TimeSpan.Zero; ComposeTable(section, document, settings, year, month, workDays, vacationDays, holidays, out var totalNet, out var totalTarget, out var totalOvertime);
TimeSpan totalTarget = TimeSpan.Zero;
TimeSpan totalOvertime = TimeSpan.Zero;
container.PaddingVertical(1, Unit.Centimetre).Column(column => ComposeSummary(section, totalNet, totalTarget, totalOvertime);
{
column.Item().Element(c => ComposeTable(c, settings, year, month, workDays, vacationDays, holidays, out totalNet, out totalTarget, out totalOvertime));
column.Item().PaddingTop(25).Element(c => ComposeSummary(c, totalNet, totalTarget, totalOvertime)); ComposeSignatures(section);
column.Item().PaddingTop(50).Element(ComposeSignatures);
});
} }
private void ComposeTable(IContainer container, AppSettings settings, int year, int month, List<WorkDay> workDays, List<VacationDay> vacationDays, List<PublicHoliday> holidays, out TimeSpan totalNetOut, out TimeSpan totalTargetOut, out TimeSpan totalOvertimeOut) private void ComposeTable(Section section, Document document, AppSettings settings, int year, int month, List<WorkDay> workDays, List<VacationDay> vacationDays, List<PublicHoliday> holidays, out TimeSpan totalNetOut, out TimeSpan totalTargetOut, out TimeSpan totalOvertimeOut)
{ {
var holidayMap = holidays.ToDictionary(h => h.Date, h => h.Name); var holidayMap = holidays.ToDictionary(h => h.Date, h => h.Name);
var vacationSet = vacationDays.Select(v => v.Date).ToHashSet(); var vacationSet = vacationDays.Select(v => v.Date).ToHashSet();
@@ -86,34 +99,34 @@ namespace timetracker.Server.Services
var totalTarget = TimeSpan.Zero; var totalTarget = TimeSpan.Zero;
var totalOvertime = TimeSpan.Zero; var totalOvertime = TimeSpan.Zero;
container.Table(table => var table = section.AddTable();
{ table.Borders.Width = 0.5;
table.ColumnsDefinition(columns => table.Borders.Color = Colors.LightGray;
{
columns.ConstantColumn(70); // Datum
columns.ConstantColumn(50); // Start
columns.ConstantColumn(50); // Ende
columns.ConstantColumn(60); // Pause
columns.ConstantColumn(60); // Netto
columns.ConstantColumn(60); // Gleitzeit
columns.RelativeColumn(); // Info
});
table.Header(header => // Define columns
{ table.AddColumn("2.5cm"); // Datum
header.Cell().Element(CellStyle).Text("Datum"); table.AddColumn("1.5cm"); // Start
header.Cell().Element(CellStyle).AlignRight().Text("Start"); table.AddColumn("1.5cm"); // Ende
header.Cell().Element(CellStyle).AlignRight().Text("Ende"); table.AddColumn("1.8cm"); // Pause
header.Cell().Element(CellStyle).AlignRight().Text("Pause"); table.AddColumn("1.8cm"); // Netto
header.Cell().Element(CellStyle).AlignRight().Text("Netto"); table.AddColumn("1.8cm"); // Gleitzeit
header.Cell().Element(CellStyle).AlignRight().Text("Gleitzeit"); table.AddColumn("6.1cm"); // Info
header.Cell().Element(CellStyle).Text("Info/Status");
static IContainer CellStyle(IContainer container) // Header Row
{ var row = table.AddRow();
return container.DefaultTextStyle(x => x.SemiBold()).PaddingVertical(5).BorderBottom(1).BorderColor(Colors.Black); row.HeadingFormat = true;
} row.Format.Font.Bold = true;
}); row.Shading.Color = Colors.White;
row.Borders.Bottom.Width = 1;
row.Borders.Bottom.Color = Colors.Black;
row.Cells[0].AddParagraph("Datum");
row.Cells[1].AddParagraph("Start").Format.Alignment = ParagraphAlignment.Right;
row.Cells[2].AddParagraph("Ende").Format.Alignment = ParagraphAlignment.Right;
row.Cells[3].AddParagraph("Pause").Format.Alignment = ParagraphAlignment.Right;
row.Cells[4].AddParagraph("Netto").Format.Alignment = ParagraphAlignment.Right;
row.Cells[5].AddParagraph("Gleitzeit").Format.Alignment = ParagraphAlignment.Right;
row.Cells[6].AddParagraph("Info/Status");
for (int day = 1; day <= daysInMonth; day++) for (int day = 1; day <= daysInMonth; day++)
{ {
@@ -153,75 +166,91 @@ namespace timetracker.Server.Services
else if (!isWorkDay) status = "Wochenende"; else if (!isWorkDay) status = "Wochenende";
var bgColor = Colors.White; var bgColor = Colors.White;
if (!isWorkDay || !string.IsNullOrEmpty(holidayName)) bgColor = Colors.Grey.Lighten4; if (!isWorkDay || !string.IsNullOrEmpty(holidayName)) bgColor = new Color(245, 245, 245); // Light Gray
if (isVacation) bgColor = Colors.Orange.Lighten4; if (isVacation) bgColor = new Color(255, 236, 179); // Light Orange
table.Cell().Element(c => CellStyle(c, bgColor)).Text(date.ToString("dd.MM.yyyy")); row = table.AddRow();
table.Cell().Element(c => CellStyle(c, bgColor)).AlignRight().Text(wd?.StartTime?.ToString(@"HH\:mm") ?? "-"); row.Shading.Color = bgColor;
table.Cell().Element(c => CellStyle(c, bgColor)).AlignRight().Text(wd?.EndTime?.ToString(@"HH\:mm") ?? "-");
table.Cell().Element(c => CellStyle(c, bgColor)).AlignRight().Text(net.HasValue ? FormatTs(breakSum) : "-");
table.Cell().Element(c => CellStyle(c, bgColor)).AlignRight().Text(net.HasValue ? FormatTs(net.Value) : "-");
var overtimeColor = overtime < TimeSpan.Zero ? Colors.Red.Darken2 : Colors.Green.Darken2; row.Cells[0].AddParagraph(date.ToString("dd.MM.yyyy"));
table.Cell().Element(c => CellStyle(c, bgColor)).AlignRight().Text(net.HasValue ? FormatTs(overtime, true) : "-").FontColor(net.HasValue && overtime != TimeSpan.Zero ? overtimeColor : Colors.Black); row.Cells[1].AddParagraph(wd?.StartTime?.ToString(@"HH\:mm") ?? "-").Format.Alignment = ParagraphAlignment.Right;
row.Cells[2].AddParagraph(wd?.EndTime?.ToString(@"HH\:mm") ?? "-").Format.Alignment = ParagraphAlignment.Right;
row.Cells[3].AddParagraph(net.HasValue ? FormatTs(breakSum) : "-").Format.Alignment = ParagraphAlignment.Right;
row.Cells[4].AddParagraph(net.HasValue ? FormatTs(net.Value) : "-").Format.Alignment = ParagraphAlignment.Right;
table.Cell().Element(c => CellStyle(c, bgColor)).Text(status).FontColor(Colors.Grey.Darken2); var overtimePara = row.Cells[5].AddParagraph(net.HasValue ? FormatTs(overtime, true) : "-");
overtimePara.Format.Alignment = ParagraphAlignment.Right;
static IContainer CellStyle(IContainer container, string bgColor) if (net.HasValue && overtime != TimeSpan.Zero)
{ {
return container.BorderBottom(1).BorderColor(Colors.Grey.Lighten3).Background(bgColor).PaddingVertical(3).PaddingHorizontal(2); overtimePara.Format.Font.Color = overtime < TimeSpan.Zero ? Colors.DarkRed : Colors.DarkGreen;
} }
var statusPara = row.Cells[6].AddParagraph(status);
statusPara.Format.Font.Color = Colors.DarkGray;
} }
});
totalNetOut = totalNet; totalNetOut = totalNet;
totalTargetOut = totalTarget; totalTargetOut = totalTarget;
totalOvertimeOut = totalOvertime; totalOvertimeOut = totalOvertime;
} }
private void ComposeSummary(IContainer container, TimeSpan totalNet, TimeSpan totalTarget, TimeSpan totalOvertime) private void ComposeSummary(Section section, TimeSpan totalNet, TimeSpan totalTarget, TimeSpan totalOvertime)
{ {
container.Background(Colors.Grey.Lighten4).Padding(10).Row(row => var paragraph = section.AddParagraph();
{ paragraph.Format.SpaceBefore = "1cm";
row.RelativeItem().Column(column =>
{
column.Item().Text("Zusammenfassung").SemiBold();
column.Item().Text($"Soll-Stunden: {FormatTs(totalTarget)}");
column.Item().Text($"Ist-Stunden: {FormatTs(totalNet)}");
var overtimeColor = totalOvertime < TimeSpan.Zero ? Colors.Red.Darken2 : Colors.Green.Darken2; var table = section.AddTable();
column.Item().Text($"Saldo: {FormatTs(totalOvertime, true)}").SemiBold().FontColor(totalOvertime != TimeSpan.Zero ? overtimeColor : Colors.Black); table.Shading.Color = new Color(245, 245, 245);
}); table.AddColumn("4cm");
}); table.AddColumn("4cm");
var row = table.AddRow();
row.Cells[0].AddParagraph("Zusammenfassung").Format.Font.Bold = true;
row.Cells[0].MergeRight = 1;
row = table.AddRow();
row.Cells[0].AddParagraph("Soll-Stunden:");
row.Cells[1].AddParagraph(FormatTs(totalTarget));
row = table.AddRow();
row.Cells[0].AddParagraph("Ist-Stunden:");
row.Cells[1].AddParagraph(FormatTs(totalNet));
row = table.AddRow();
row.Cells[0].AddParagraph("Saldo:");
var saldoPara = row.Cells[1].AddParagraph(FormatTs(totalOvertime, true));
saldoPara.Format.Font.Bold = true;
if (totalOvertime != TimeSpan.Zero)
{
saldoPara.Format.Font.Color = totalOvertime < TimeSpan.Zero ? Colors.DarkRed : Colors.DarkGreen;
}
} }
private void ComposeSignatures(IContainer container) private void ComposeSignatures(Section section)
{ {
container.Row(row => var table = section.AddTable();
{ table.Format.SpaceBefore = "2cm";
row.RelativeItem().Column(column => table.AddColumn("7cm");
{ table.AddColumn("2cm");
column.Item().LineHorizontal(1); table.AddColumn("7cm");
column.Item().PaddingTop(5).Text("Datum, Unterschrift Mitarbeiter").FontSize(10);
}); var row = table.AddRow();
row.ConstantItem(50); // space row.Cells[0].Borders.Bottom.Width = 1;
row.RelativeItem().Column(column => row.Cells[2].Borders.Bottom.Width = 1;
{
column.Item().LineHorizontal(1); row = table.AddRow();
column.Item().PaddingTop(5).Text("Datum, Unterschrift Arbeitgeber").FontSize(10); row.Cells[0].AddParagraph("Datum, Unterschrift Mitarbeiter").Format.Font.Size = 8;
}); row.Cells[2].AddParagraph("Datum, Unterschrift Arbeitgeber").Format.Font.Size = 8;
});
} }
private void ComposeFooter(IContainer container) private void ComposeFooter(Section section)
{ {
container.AlignCenter().Text(x => var paragraph = section.Footers.Primary.AddParagraph();
{ paragraph.AddText("Seite ");
x.Span("Seite "); paragraph.AddPageField();
x.CurrentPageNumber(); paragraph.AddText(" von ");
x.Span(" von "); paragraph.AddNumPagesField();
x.TotalPages(); paragraph.Format.Alignment = ParagraphAlignment.Center;
});
} }
private static string FormatTs(TimeSpan ts, bool sign = false) private static string FormatTs(TimeSpan ts, bool sign = false)
@@ -1838,3 +1838,323 @@ SELECT "p"."Id", "p"."Counties", "p"."Date", "p"."Name"
FROM "PublicHolidays" AS "p" FROM "PublicHolidays" AS "p"
WHERE CAST(strftime('%Y', "p"."Date") AS INTEGER) = @year WHERE CAST(strftime('%Y', "p"."Date") AS INTEGER) = @year
ORDER BY "p"."Date" ORDER BY "p"."Date"
2026-06-26 23:14:21.574 +02:00 [INF] File logging active. Writing logs to /Users/marcwieland/Uni/Master/Projects/Timetracker/timetracker/timetracker.Server/logs
2026-06-26 23:14:22.140 +02:00 [WRN] Entity 'WorkDay' has a global query filter defined and is the required end of a relationship with the entity 'BreakEntry'. This may lead to unexpected results when the required entity is filtered out. Either configure the navigation as optional, or define matching query filters for both entities in the navigation. See https://go.microsoft.com/fwlink/?linkid=2131316 for more information.
2026-06-26 23:14:22.205 +02:00 [INF] Acquiring an exclusive lock for migration application. See https://aka.ms/efcore-docs-migrations-lock for more information if this takes too long.
2026-06-26 23:14:22.216 +02:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
SELECT COUNT(*) FROM "sqlite_master" WHERE "name" = '__EFMigrationsLock' AND "type" = 'table';
2026-06-26 23:14:22.220 +02:00 [INF] Executed DbCommand (1ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
INSERT OR IGNORE INTO "__EFMigrationsLock"("Id", "Timestamp") VALUES(1, '2026-06-26 21:14:22.219046+00:00');
SELECT changes();
2026-06-26 23:14:22.273 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
"MigrationId" TEXT NOT NULL CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY,
"ProductVersion" TEXT NOT NULL
);
2026-06-26 23:14:22.279 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
SELECT COUNT(*) FROM "sqlite_master" WHERE "name" = '__EFMigrationsHistory' AND "type" = 'table';
2026-06-26 23:14:22.281 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
SELECT "MigrationId", "ProductVersion"
FROM "__EFMigrationsHistory"
ORDER BY "MigrationId";
2026-06-26 23:14:22.284 +02:00 [INF] No migrations were applied. The database is already up to date.
2026-06-26 23:14:22.286 +02:00 [INF] Executed DbCommand (1ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
DELETE FROM "__EFMigrationsLock";
2026-06-26 23:14:22.412 +02:00 [INF] Executed DbCommand (1ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
SELECT "u"."Id", "u"."IsTenantAdmin", "u"."PasswordHash", "u"."PasswordSalt", "u"."TenantId", "u"."Username"
FROM "Users" AS "u"
WHERE "u"."TenantId" IS NULL AND "u"."Username" = 'marc'
LIMIT 1
2026-06-26 23:14:22.920 +02:00 [INF] Executed DbCommand (2ms) [Parameters=[@userId='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "a"."Id", "a"."DailyTargetHours", "a"."FlexTimeStartDate", "a"."FlexTimeStartingBalanceHours", "a"."GermanState", "a"."MinimumBreakMinutes", "a"."TenantId", "a"."UserId", "a"."VacationDaysPerYear", "a"."WorkFriday", "a"."WorkMonday", "a"."WorkSaturday", "a"."WorkSunday", "a"."WorkThursday", "a"."WorkTuesday", "a"."WorkWednesday"
FROM "AppSettings" AS "a"
WHERE "a"."TenantId" IS NULL AND "a"."UserId" = @userId
LIMIT 1
2026-06-26 23:14:22.958 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@userId='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "a"."Id", "a"."DailyTargetHours", "a"."FlexTimeStartDate", "a"."FlexTimeStartingBalanceHours", "a"."GermanState", "a"."MinimumBreakMinutes", "a"."TenantId", "a"."UserId", "a"."VacationDaysPerYear", "a"."WorkFriday", "a"."WorkMonday", "a"."WorkSaturday", "a"."WorkSunday", "a"."WorkThursday", "a"."WorkTuesday", "a"."WorkWednesday"
FROM "AppSettings" AS "a"
WHERE "a"."TenantId" IS NULL AND "a"."UserId" = @userId
LIMIT 1
2026-06-26 23:14:22.969 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@year='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "p"."Id", "p"."Counties", "p"."Date", "p"."Name"
FROM "PublicHolidays" AS "p"
WHERE CAST(strftime('%Y', "p"."Date") AS INTEGER) = @year
ORDER BY "p"."Date"
2026-06-26 23:14:23.006 +02:00 [INF] Executed DbCommand (1ms) [Parameters=[@userId='?' (DbType = Int32), @monday='?' (DbType = Date), @AddDays='?' (DbType = Date)], CommandType='"Text"', CommandTimeout='30']
SELECT "w"."Id", "w"."Date", "w"."EndTime", "w"."StartTime", "w"."TenantId", "w"."UserId", "b"."Id", "b"."EndTime", "b"."StartTime", "b"."WorkDayId"
FROM "WorkDays" AS "w"
LEFT JOIN "BreakEntries" AS "b" ON "w"."Id" = "b"."WorkDayId"
WHERE "w"."TenantId" IS NULL AND "w"."UserId" = @userId AND "w"."Date" >= @monday AND "w"."Date" < @AddDays
ORDER BY "w"."Date", "w"."Id"
2026-06-26 23:14:23.011 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@userId='?' (DbType = Int32), @year='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "v"."Id", "v"."Date", "v"."Note", "v"."TenantId", "v"."UserId"
FROM "VacationDays" AS "v"
WHERE "v"."TenantId" IS NULL AND "v"."UserId" = @userId AND CAST(strftime('%Y', "v"."Date") AS INTEGER) = @year
ORDER BY "v"."Date"
2026-06-26 23:14:23.026 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@_8__locals1_userId='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "w"."Date"
FROM "WorkDays" AS "w"
WHERE "w"."TenantId" IS NULL AND "w"."UserId" = @_8__locals1_userId
ORDER BY "w"."Date"
LIMIT 1
2026-06-26 23:14:23.052 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@_8__locals1_userId='?' (DbType = Int32), @startDate='?' (DbType = Date), @endDate='?' (DbType = Date)], CommandType='"Text"', CommandTimeout='30']
SELECT "w"."Id", "w"."Date", "w"."EndTime", "w"."StartTime", "w"."TenantId", "w"."UserId", "b"."Id", "b"."EndTime", "b"."StartTime", "b"."WorkDayId"
FROM "WorkDays" AS "w"
LEFT JOIN "BreakEntries" AS "b" ON "w"."Id" = "b"."WorkDayId"
WHERE "w"."TenantId" IS NULL AND "w"."UserId" = @_8__locals1_userId AND "w"."Date" >= @startDate AND "w"."Date" <= @endDate
ORDER BY "w"."Id"
2026-06-26 23:14:23.072 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@startDate='?' (DbType = Date), @endDate='?' (DbType = Date)], CommandType='"Text"', CommandTimeout='30']
SELECT "p"."Id", "p"."Counties", "p"."Date", "p"."Name"
FROM "PublicHolidays" AS "p"
WHERE "p"."Date" >= @startDate AND "p"."Date" <= @endDate
2026-06-26 23:14:23.083 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@_8__locals1_userId='?' (DbType = Int32), @startDate='?' (DbType = Date), @endDate='?' (DbType = Date)], CommandType='"Text"', CommandTimeout='30']
SELECT "v"."Date"
FROM "VacationDays" AS "v"
WHERE "v"."TenantId" IS NULL AND "v"."UserId" = @_8__locals1_userId AND "v"."Date" >= @startDate AND "v"."Date" <= @endDate
2026-06-26 23:14:23.879 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@userId='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "a"."Id", "a"."DailyTargetHours", "a"."FlexTimeStartDate", "a"."FlexTimeStartingBalanceHours", "a"."GermanState", "a"."MinimumBreakMinutes", "a"."TenantId", "a"."UserId", "a"."VacationDaysPerYear", "a"."WorkFriday", "a"."WorkMonday", "a"."WorkSaturday", "a"."WorkSunday", "a"."WorkThursday", "a"."WorkTuesday", "a"."WorkWednesday"
FROM "AppSettings" AS "a"
WHERE "a"."TenantId" IS NULL AND "a"."UserId" = @userId
LIMIT 1
2026-06-26 23:14:28.518 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@year='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "p"."Id", "p"."Counties", "p"."Date", "p"."Name"
FROM "PublicHolidays" AS "p"
WHERE CAST(strftime('%Y', "p"."Date") AS INTEGER) = @year
ORDER BY "p"."Date"
2026-06-26 23:14:28.518 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@userId='?' (DbType = Int32), @year='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "v"."Id", "v"."Date", "v"."Note", "v"."TenantId", "v"."UserId"
FROM "VacationDays" AS "v"
WHERE "v"."TenantId" IS NULL AND "v"."UserId" = @userId AND CAST(strftime('%Y', "v"."Date") AS INTEGER) = @year
ORDER BY "v"."Date"
2026-06-26 23:14:28.527 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@_8__locals1_userId='?' (DbType = Int32), @from='?' (DbType = Date), @to='?' (DbType = Date)], CommandType='"Text"', CommandTimeout='30']
SELECT "w"."Id", "w"."Date", "w"."EndTime", "w"."StartTime", "w"."TenantId", "w"."UserId", "b"."Id", "b"."EndTime", "b"."StartTime", "b"."WorkDayId"
FROM "WorkDays" AS "w"
LEFT JOIN "BreakEntries" AS "b" ON "w"."Id" = "b"."WorkDayId"
WHERE "w"."TenantId" IS NULL AND "w"."UserId" = @_8__locals1_userId AND "w"."Date" >= @from AND "w"."Date" < @to
ORDER BY "w"."Date", "w"."Id"
2026-06-26 23:14:33.174 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@userId='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "u"."Id", "u"."IsTenantAdmin", "u"."PasswordHash", "u"."PasswordSalt", "u"."TenantId", "u"."Username"
FROM "Users" AS "u"
WHERE "u"."Id" = @userId
LIMIT 1
2026-06-26 23:14:33.175 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@_8__locals1_userId='?' (DbType = Int32), @from='?' (DbType = Date), @to='?' (DbType = Date)], CommandType='"Text"', CommandTimeout='30']
SELECT "w"."Id", "w"."Date", "w"."EndTime", "w"."StartTime", "w"."TenantId", "w"."UserId", "b"."Id", "b"."EndTime", "b"."StartTime", "b"."WorkDayId"
FROM "WorkDays" AS "w"
LEFT JOIN "BreakEntries" AS "b" ON "w"."Id" = "b"."WorkDayId"
WHERE "w"."TenantId" IS NULL AND "w"."UserId" = @_8__locals1_userId AND "w"."Date" >= @from AND "w"."Date" < @to
ORDER BY "w"."Date", "w"."Id"
2026-06-26 23:14:33.176 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@userId='?' (DbType = Int32), @year='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "v"."Id", "v"."Date", "v"."Note", "v"."TenantId", "v"."UserId"
FROM "VacationDays" AS "v"
WHERE "v"."TenantId" IS NULL AND "v"."UserId" = @userId AND CAST(strftime('%Y', "v"."Date") AS INTEGER) = @year
ORDER BY "v"."Date"
2026-06-26 23:14:33.177 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@userId='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "a"."Id", "a"."DailyTargetHours", "a"."FlexTimeStartDate", "a"."FlexTimeStartingBalanceHours", "a"."GermanState", "a"."MinimumBreakMinutes", "a"."TenantId", "a"."UserId", "a"."VacationDaysPerYear", "a"."WorkFriday", "a"."WorkMonday", "a"."WorkSaturday", "a"."WorkSunday", "a"."WorkThursday", "a"."WorkTuesday", "a"."WorkWednesday"
FROM "AppSettings" AS "a"
WHERE "a"."TenantId" IS NULL AND "a"."UserId" = @userId
LIMIT 1
2026-06-26 23:14:33.178 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@year='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "p"."Id", "p"."Counties", "p"."Date", "p"."Name"
FROM "PublicHolidays" AS "p"
WHERE CAST(strftime('%Y', "p"."Date") AS INTEGER) = @year
ORDER BY "p"."Date"
2026-06-26 23:14:33.200 +02:00 [ERR] An unhandled exception has occurred while executing the request.
System.InvalidOperationException: The font 'Courier New' cannot be resolved for predefined error font. Use another font name or fix your font resolver. See https://docs.pdfsharp.net/link/migradoc-font-resolving-6.2.html and https://docs.pdfsharp.net/link/font-resolving.html for further information.
---> System.InvalidOperationException: No appropriate font found for family name 'Courier New'. Implement IFontResolver and assign to 'GlobalFontSettings.FontResolver' to use fonts. See https://docs.pdfsharp.net/link/font-resolving.html
at PdfSharp.Drawing.XGlyphTypeface.GetOrCreateFrom(String familyName, FontResolvingOptions fontResolvingOptions)
at PdfSharp.Drawing.XFont.Initialize()
at PdfSharp.Drawing.XFont..ctor(String familyName, Double emSize, XFontStyleEx style, XPdfFontOptions pdfOptions)
at PdfSharp.Drawing.XFont..ctor(String familyName, Double emSize, XFontStyleEx style)
at MigraDoc.Rendering.DocumentRenderer.PredefinedFontsAndChars.CreateFont(String familyName, Double emSize, XFontStyleEx style, String propertyDescription)
--- End of inner exception stack trace ---
at MigraDoc.Rendering.DocumentRenderer.PredefinedFontsAndChars.CreateFont(String familyName, Double emSize, XFontStyleEx style, String propertyDescription)
at MigraDoc.Rendering.DocumentRenderer.PredefinedFontsAndChars.get_ErrorFont()
at MigraDoc.Rendering.DocumentRenderer.PredefinedFontsAndChars.CreateAllFixedFonts()
at MigraDoc.Rendering.PdfDocumentRenderer.PrepareDocumentRenderer(Boolean prepareCompletely)
at MigraDoc.Rendering.PdfDocumentRenderer.PrepareRenderPages()
at MigraDoc.Rendering.PdfDocumentRenderer.RenderDocument()
at timetracker.Server.Services.PdfGeneratorService.GenerateTimesheetPdf(User user, Tenant tenant, AppSettings settings, Int32 year, Int32 month, List`1 workDays, List`1 vacationDays, List`1 holidays) in /Users/marcwieland/Uni/Master/Projects/Timetracker/timetracker/timetracker.Server/Services/PdfGeneratorService.cs:line 50
at Program.<>c.<<<Main>$>b__0_27>d.MoveNext() in /Users/marcwieland/Uni/Master/Projects/Timetracker/timetracker/timetracker.Server/Program.cs:line 779
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Http.RequestDelegateFactory.ExecuteTaskResult[T](Task`1 task, HttpContext httpContext)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
2026-06-26 23:14:33.313 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@userId='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "a"."Id", "a"."DailyTargetHours", "a"."FlexTimeStartDate", "a"."FlexTimeStartingBalanceHours", "a"."GermanState", "a"."MinimumBreakMinutes", "a"."TenantId", "a"."UserId", "a"."VacationDaysPerYear", "a"."WorkFriday", "a"."WorkMonday", "a"."WorkSaturday", "a"."WorkSunday", "a"."WorkThursday", "a"."WorkTuesday", "a"."WorkWednesday"
FROM "AppSettings" AS "a"
WHERE "a"."TenantId" IS NULL AND "a"."UserId" = @userId
LIMIT 1
2026-06-26 23:14:57.355 +02:00 [INF] File logging active. Writing logs to /Users/marcwieland/Uni/Master/Projects/Timetracker/timetracker/timetracker.Server/logs
2026-06-26 23:14:57.589 +02:00 [WRN] Entity 'WorkDay' has a global query filter defined and is the required end of a relationship with the entity 'BreakEntry'. This may lead to unexpected results when the required entity is filtered out. Either configure the navigation as optional, or define matching query filters for both entities in the navigation. See https://go.microsoft.com/fwlink/?linkid=2131316 for more information.
2026-06-26 23:14:57.653 +02:00 [INF] Acquiring an exclusive lock for migration application. See https://aka.ms/efcore-docs-migrations-lock for more information if this takes too long.
2026-06-26 23:14:57.665 +02:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
SELECT COUNT(*) FROM "sqlite_master" WHERE "name" = '__EFMigrationsLock' AND "type" = 'table';
2026-06-26 23:14:57.669 +02:00 [INF] Executed DbCommand (2ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
INSERT OR IGNORE INTO "__EFMigrationsLock"("Id", "Timestamp") VALUES(1, '2026-06-26 21:14:57.667524+00:00');
SELECT changes();
2026-06-26 23:14:57.721 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
"MigrationId" TEXT NOT NULL CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY,
"ProductVersion" TEXT NOT NULL
);
2026-06-26 23:14:57.727 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
SELECT COUNT(*) FROM "sqlite_master" WHERE "name" = '__EFMigrationsHistory' AND "type" = 'table';
2026-06-26 23:14:57.729 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
SELECT "MigrationId", "ProductVersion"
FROM "__EFMigrationsHistory"
ORDER BY "MigrationId";
2026-06-26 23:14:57.732 +02:00 [INF] No migrations were applied. The database is already up to date.
2026-06-26 23:14:57.734 +02:00 [INF] Executed DbCommand (1ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
DELETE FROM "__EFMigrationsLock";
2026-06-26 23:14:57.860 +02:00 [INF] Executed DbCommand (1ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
SELECT "u"."Id", "u"."IsTenantAdmin", "u"."PasswordHash", "u"."PasswordSalt", "u"."TenantId", "u"."Username"
FROM "Users" AS "u"
WHERE "u"."TenantId" IS NULL AND "u"."Username" = 'marc'
LIMIT 1
2026-06-26 23:14:58.019 +02:00 [ERR] Hosting failed to start
System.IO.IOException: Failed to bind to address http://127.0.0.1:5065: address already in use.
---> Microsoft.AspNetCore.Connections.AddressInUseException: Address already in use
---> System.Net.Sockets.SocketException (48): Address already in use
at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, Boolean disconnectOnFailure, String callerName)
at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress)
at System.Net.Sockets.Socket.Bind(EndPoint localEP)
at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions.CreateDefaultBoundListenSocket(EndPoint endpoint)
at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind()
--- End of inner exception stack trace ---
at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind()
at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory.BindAsync(EndPoint endpoint, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig endpointConfig, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass28_0`1.<<StartAsync>g__OnBind|0>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.LocalhostListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.AddressesStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.Internal.Host.<StartAsync>b__14_1(IHostedService service, CancellationToken token)
at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation)
2026-06-26 23:16:46.101 +02:00 [INF] File logging active. Writing logs to /Users/marcwieland/Uni/Master/Projects/Timetracker/timetracker/timetracker.Server/logs
2026-06-26 23:16:46.558 +02:00 [WRN] Entity 'WorkDay' has a global query filter defined and is the required end of a relationship with the entity 'BreakEntry'. This may lead to unexpected results when the required entity is filtered out. Either configure the navigation as optional, or define matching query filters for both entities in the navigation. See https://go.microsoft.com/fwlink/?linkid=2131316 for more information.
2026-06-26 23:16:46.626 +02:00 [INF] Acquiring an exclusive lock for migration application. See https://aka.ms/efcore-docs-migrations-lock for more information if this takes too long.
2026-06-26 23:16:46.649 +02:00 [INF] Executed DbCommand (5ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
SELECT COUNT(*) FROM "sqlite_master" WHERE "name" = '__EFMigrationsLock' AND "type" = 'table';
2026-06-26 23:16:46.658 +02:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
INSERT OR IGNORE INTO "__EFMigrationsLock"("Id", "Timestamp") VALUES(1, '2026-06-26 21:16:46.655104+00:00');
SELECT changes();
2026-06-26 23:16:46.737 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
"MigrationId" TEXT NOT NULL CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY,
"ProductVersion" TEXT NOT NULL
);
2026-06-26 23:16:46.743 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
SELECT COUNT(*) FROM "sqlite_master" WHERE "name" = '__EFMigrationsHistory' AND "type" = 'table';
2026-06-26 23:16:46.745 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
SELECT "MigrationId", "ProductVersion"
FROM "__EFMigrationsHistory"
ORDER BY "MigrationId";
2026-06-26 23:16:46.748 +02:00 [INF] No migrations were applied. The database is already up to date.
2026-06-26 23:16:46.749 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
DELETE FROM "__EFMigrationsLock";
2026-06-26 23:16:46.876 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
SELECT "u"."Id", "u"."IsTenantAdmin", "u"."PasswordHash", "u"."PasswordSalt", "u"."TenantId", "u"."Username"
FROM "Users" AS "u"
WHERE "u"."TenantId" IS NULL AND "u"."Username" = 'marc'
LIMIT 1
2026-06-26 23:16:47.560 +02:00 [INF] Executed DbCommand (2ms) [Parameters=[@userId='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "a"."Id", "a"."DailyTargetHours", "a"."FlexTimeStartDate", "a"."FlexTimeStartingBalanceHours", "a"."GermanState", "a"."MinimumBreakMinutes", "a"."TenantId", "a"."UserId", "a"."VacationDaysPerYear", "a"."WorkFriday", "a"."WorkMonday", "a"."WorkSaturday", "a"."WorkSunday", "a"."WorkThursday", "a"."WorkTuesday", "a"."WorkWednesday"
FROM "AppSettings" AS "a"
WHERE "a"."TenantId" IS NULL AND "a"."UserId" = @userId
LIMIT 1
2026-06-26 23:16:47.601 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@userId='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "a"."Id", "a"."DailyTargetHours", "a"."FlexTimeStartDate", "a"."FlexTimeStartingBalanceHours", "a"."GermanState", "a"."MinimumBreakMinutes", "a"."TenantId", "a"."UserId", "a"."VacationDaysPerYear", "a"."WorkFriday", "a"."WorkMonday", "a"."WorkSaturday", "a"."WorkSunday", "a"."WorkThursday", "a"."WorkTuesday", "a"."WorkWednesday"
FROM "AppSettings" AS "a"
WHERE "a"."TenantId" IS NULL AND "a"."UserId" = @userId
LIMIT 1
2026-06-26 23:16:47.613 +02:00 [INF] Executed DbCommand (1ms) [Parameters=[@year='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "p"."Id", "p"."Counties", "p"."Date", "p"."Name"
FROM "PublicHolidays" AS "p"
WHERE CAST(strftime('%Y', "p"."Date") AS INTEGER) = @year
ORDER BY "p"."Date"
2026-06-26 23:16:47.651 +02:00 [INF] Executed DbCommand (1ms) [Parameters=[@userId='?' (DbType = Int32), @monday='?' (DbType = Date), @AddDays='?' (DbType = Date)], CommandType='"Text"', CommandTimeout='30']
SELECT "w"."Id", "w"."Date", "w"."EndTime", "w"."StartTime", "w"."TenantId", "w"."UserId", "b"."Id", "b"."EndTime", "b"."StartTime", "b"."WorkDayId"
FROM "WorkDays" AS "w"
LEFT JOIN "BreakEntries" AS "b" ON "w"."Id" = "b"."WorkDayId"
WHERE "w"."TenantId" IS NULL AND "w"."UserId" = @userId AND "w"."Date" >= @monday AND "w"."Date" < @AddDays
ORDER BY "w"."Date", "w"."Id"
2026-06-26 23:16:47.656 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@userId='?' (DbType = Int32), @year='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "v"."Id", "v"."Date", "v"."Note", "v"."TenantId", "v"."UserId"
FROM "VacationDays" AS "v"
WHERE "v"."TenantId" IS NULL AND "v"."UserId" = @userId AND CAST(strftime('%Y', "v"."Date") AS INTEGER) = @year
ORDER BY "v"."Date"
2026-06-26 23:16:47.668 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@_8__locals1_userId='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "w"."Date"
FROM "WorkDays" AS "w"
WHERE "w"."TenantId" IS NULL AND "w"."UserId" = @_8__locals1_userId
ORDER BY "w"."Date"
LIMIT 1
2026-06-26 23:16:47.677 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@_8__locals1_userId='?' (DbType = Int32), @startDate='?' (DbType = Date), @endDate='?' (DbType = Date)], CommandType='"Text"', CommandTimeout='30']
SELECT "w"."Id", "w"."Date", "w"."EndTime", "w"."StartTime", "w"."TenantId", "w"."UserId", "b"."Id", "b"."EndTime", "b"."StartTime", "b"."WorkDayId"
FROM "WorkDays" AS "w"
LEFT JOIN "BreakEntries" AS "b" ON "w"."Id" = "b"."WorkDayId"
WHERE "w"."TenantId" IS NULL AND "w"."UserId" = @_8__locals1_userId AND "w"."Date" >= @startDate AND "w"."Date" <= @endDate
ORDER BY "w"."Id"
2026-06-26 23:16:47.688 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@startDate='?' (DbType = Date), @endDate='?' (DbType = Date)], CommandType='"Text"', CommandTimeout='30']
SELECT "p"."Id", "p"."Counties", "p"."Date", "p"."Name"
FROM "PublicHolidays" AS "p"
WHERE "p"."Date" >= @startDate AND "p"."Date" <= @endDate
2026-06-26 23:16:47.694 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@_8__locals1_userId='?' (DbType = Int32), @startDate='?' (DbType = Date), @endDate='?' (DbType = Date)], CommandType='"Text"', CommandTimeout='30']
SELECT "v"."Date"
FROM "VacationDays" AS "v"
WHERE "v"."TenantId" IS NULL AND "v"."UserId" = @_8__locals1_userId AND "v"."Date" >= @startDate AND "v"."Date" <= @endDate
2026-06-26 23:16:48.500 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@userId='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "a"."Id", "a"."DailyTargetHours", "a"."FlexTimeStartDate", "a"."FlexTimeStartingBalanceHours", "a"."GermanState", "a"."MinimumBreakMinutes", "a"."TenantId", "a"."UserId", "a"."VacationDaysPerYear", "a"."WorkFriday", "a"."WorkMonday", "a"."WorkSaturday", "a"."WorkSunday", "a"."WorkThursday", "a"."WorkTuesday", "a"."WorkWednesday"
FROM "AppSettings" AS "a"
WHERE "a"."TenantId" IS NULL AND "a"."UserId" = @userId
LIMIT 1
2026-06-26 23:16:50.033 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@userId='?' (DbType = Int32), @year='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "v"."Id", "v"."Date", "v"."Note", "v"."TenantId", "v"."UserId"
FROM "VacationDays" AS "v"
WHERE "v"."TenantId" IS NULL AND "v"."UserId" = @userId AND CAST(strftime('%Y', "v"."Date") AS INTEGER) = @year
ORDER BY "v"."Date"
2026-06-26 23:16:50.033 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@year='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "p"."Id", "p"."Counties", "p"."Date", "p"."Name"
FROM "PublicHolidays" AS "p"
WHERE CAST(strftime('%Y', "p"."Date") AS INTEGER) = @year
ORDER BY "p"."Date"
2026-06-26 23:16:50.041 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@_8__locals1_userId='?' (DbType = Int32), @from='?' (DbType = Date), @to='?' (DbType = Date)], CommandType='"Text"', CommandTimeout='30']
SELECT "w"."Id", "w"."Date", "w"."EndTime", "w"."StartTime", "w"."TenantId", "w"."UserId", "b"."Id", "b"."EndTime", "b"."StartTime", "b"."WorkDayId"
FROM "WorkDays" AS "w"
LEFT JOIN "BreakEntries" AS "b" ON "w"."Id" = "b"."WorkDayId"
WHERE "w"."TenantId" IS NULL AND "w"."UserId" = @_8__locals1_userId AND "w"."Date" >= @from AND "w"."Date" < @to
ORDER BY "w"."Date", "w"."Id"
2026-06-26 23:16:51.302 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@userId='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "u"."Id", "u"."IsTenantAdmin", "u"."PasswordHash", "u"."PasswordSalt", "u"."TenantId", "u"."Username"
FROM "Users" AS "u"
WHERE "u"."Id" = @userId
LIMIT 1
2026-06-26 23:16:51.303 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@_8__locals1_userId='?' (DbType = Int32), @from='?' (DbType = Date), @to='?' (DbType = Date)], CommandType='"Text"', CommandTimeout='30']
SELECT "w"."Id", "w"."Date", "w"."EndTime", "w"."StartTime", "w"."TenantId", "w"."UserId", "b"."Id", "b"."EndTime", "b"."StartTime", "b"."WorkDayId"
FROM "WorkDays" AS "w"
LEFT JOIN "BreakEntries" AS "b" ON "w"."Id" = "b"."WorkDayId"
WHERE "w"."TenantId" IS NULL AND "w"."UserId" = @_8__locals1_userId AND "w"."Date" >= @from AND "w"."Date" < @to
ORDER BY "w"."Date", "w"."Id"
2026-06-26 23:16:51.303 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@userId='?' (DbType = Int32), @year='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "v"."Id", "v"."Date", "v"."Note", "v"."TenantId", "v"."UserId"
FROM "VacationDays" AS "v"
WHERE "v"."TenantId" IS NULL AND "v"."UserId" = @userId AND CAST(strftime('%Y', "v"."Date") AS INTEGER) = @year
ORDER BY "v"."Date"
2026-06-26 23:16:51.303 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@userId='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "a"."Id", "a"."DailyTargetHours", "a"."FlexTimeStartDate", "a"."FlexTimeStartingBalanceHours", "a"."GermanState", "a"."MinimumBreakMinutes", "a"."TenantId", "a"."UserId", "a"."VacationDaysPerYear", "a"."WorkFriday", "a"."WorkMonday", "a"."WorkSaturday", "a"."WorkSunday", "a"."WorkThursday", "a"."WorkTuesday", "a"."WorkWednesday"
FROM "AppSettings" AS "a"
WHERE "a"."TenantId" IS NULL AND "a"."UserId" = @userId
LIMIT 1
2026-06-26 23:16:51.304 +02:00 [INF] Executed DbCommand (0ms) [Parameters=[@year='?' (DbType = Int32)], CommandType='"Text"', CommandTimeout='30']
SELECT "p"."Id", "p"."Counties", "p"."Date", "p"."Name"
FROM "PublicHolidays" AS "p"
WHERE CAST(strftime('%Y', "p"."Date") AS INTEGER) = @year
ORDER BY "p"."Date"
+5 -1
View File
@@ -14,7 +14,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.8" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.8" />
<PackageReference Include="MudBlazor" Version="9.4.0" /> <PackageReference Include="MudBlazor" Version="9.4.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.2" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.2" />
<PackageReference Include="QuestPDF" Version="2026.6.1" /> <PackageReference Include="PDFsharp-MigraDoc" Version="6.2.4" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" /> <PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.3" /> <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.3" />
@@ -25,4 +25,8 @@
<ProjectReference Include="..\timetracker.Client\timetracker.Client.csproj" /> <ProjectReference Include="..\timetracker.Client\timetracker.Client.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Fonts\*.ttf" />
</ItemGroup>
</Project> </Project>