using System.Globalization; using System.Net; using ClosedXML.Excel; using Microsoft.EntityFrameworkCore; using TrafagSalesExporter.Data; using TrafagSalesExporter.Services; var builder = WebApplication.CreateBuilder(args); var databasePath = ResolveDatabasePath(builder.Configuration["FinanceProbe:DatabasePath"]); builder.Services.AddDbContextFactory(options => options.UseSqlite($"Data Source={databasePath};Default Timeout=60")); builder.Services.AddSingleton(); var app = builder.Build(); app.MapGet("/", () => Results.Redirect("/finance")); app.MapGet("/finance", async (IFinanceReconciliationService finance) => { var rows = await finance.BuildNetSalesReferenceRowsAsync(2025); var excelReferences = LoadCheckedExcelReferences(ResolveCheckedExcelPath()); return Results.Content(BuildPage(rows, databasePath, excelReferences), "text/html; charset=utf-8"); }); app.Run(); static string ResolveDatabasePath(string? configuredPath) { if (!string.IsNullOrWhiteSpace(configuredPath)) return Path.GetFullPath(configuredPath); foreach (var start in new[] { Directory.GetCurrentDirectory(), AppContext.BaseDirectory }) { var directory = new DirectoryInfo(start); while (directory is not null) { var candidate = Path.Combine(directory.FullName, "trafag_exporter.db"); if (File.Exists(candidate)) return candidate; directory = directory.Parent; } } return Path.Combine(Directory.GetCurrentDirectory(), "trafag_exporter.db"); } static string? ResolveCheckedExcelPath() { foreach (var start in new[] { Directory.GetCurrentDirectory(), AppContext.BaseDirectory }) { var directory = new DirectoryInfo(start); while (directory is not null) { var candidate = Path.Combine(directory.FullName, "check.xlsx"); if (File.Exists(candidate)) return candidate; directory = directory.Parent; } } return null; } static Dictionary LoadCheckedExcelReferences(string? path) { if (string.IsNullOrWhiteSpace(path) || !File.Exists(path)) return []; using var workbook = new XLWorkbook(path); var worksheet = workbook.Worksheets.First(); var references = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var row in worksheet.RowsUsed().Skip(1)) { var label = row.Cell(1).GetString().Trim(); if (string.IsNullOrWhiteSpace(label) || label.Equals("Total TR Gruppe", StringComparison.OrdinalIgnoreCase)) continue; references[label] = new CheckedExcelReference { Label = label, LocalCurrencyValue = ReadNullableDecimal(row.Cell(2)), ChfValue = ReadNullableDecimal(row.Cell(3)), PowerBiValue = ReadNullableDecimal(row.Cell(5)), Status = row.Cell(6).GetString().Trim() }; } return references; } static decimal? ReadNullableDecimal(IXLCell cell) { if (cell.IsEmpty()) return null; return cell.TryGetValue(out var value) ? value : null; } static string BuildPage( IReadOnlyList rows, string databasePath, IReadOnlyDictionary excelReferences) { var generatedAt = DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss", CultureInfo.GetCultureInfo("de-CH")); var okCount = rows.Count(r => r.Status == "OK"); var checkCount = rows.Count(r => r.Status == "Pruefen"); var missingCount = rows.Count(r => r.Status == "Keine Daten"); var excelCount = excelReferences.Count; return $$""" Finance Probe

Finance Probe - Net Sales Actuals 2025

Vergleich gegen geprüfte Referenzwerte aus check.xlsx / Power BI Stand 29.04.2026 DB: {{Html(databasePath)}} Excel-Referenzen gelesen: {{excelCount}} Aktualisiert: {{Html(generatedAt)}}
{{rows.Count}}Standorte
{{okCount}}OK
{{checkCount}}Pruefen
{{missingCount}}Keine Daten
{{string.Join(Environment.NewLine, rows.Select(row => BuildRow(row, excelReferences)))}}
Status Firma Gewaehlte Abgrenzung Ist-Waehrung Ist 2025 Referenz-Waehrung Referenz Excel LC Excel CHF Excel Power BI Excel Status Differenz Ohne IC Diff. Waehrung Zeilen Varianten
"""; } static string BuildRow(NetSalesReferenceRow row, IReadOnlyDictionary excelReferences) { var statusClass = row.Status.Replace(" ", string.Empty); excelReferences.TryGetValue(row.Label, out var excelReference); return $$""" {{Html(row.Status)}} {{Html(row.Label)}}
{{Html(row.Key)}} / {{Html(row.ReferenceSource)}}
{{Html(row.ValueField)}} {{Html(row.ActualCurrency)}} {{Amount(row.ActualValue)}} {{Html(row.ReferenceCurrency)}} {{Amount(row.ReferenceValue)}} {{Amount(excelReference?.LocalCurrencyValue)}} {{Amount(excelReference?.ChfValue)}} {{Amount(excelReference?.PowerBiValue)}} {{Html(excelReference?.Status)}} {{Amount(row.Difference)}} {{Amount(row.DifferenceExcludingIntercompany)}} {{Html(row.Currencies)}} {{row.RowCount}} {{BuildCandidateDetails(row)}} """; } static string BuildCandidateDetails(NetSalesReferenceRow row) { if (row.Candidates.Count == 0) return "Keine Varianten"; var candidateRows = string.Join(Environment.NewLine, row.Candidates.Select(candidate => $$""" {{Html(candidate.Label)}} {{Html(candidate.Currency)}} {{Amount(candidate.Value)}} {{Amount(candidate.Difference)}} {{Amount(candidate.IntercompanyValue)}} {{Amount(candidate.DifferenceExcludingIntercompany)}} """)); return $$"""
{{row.Candidates.Count}} Varianten anzeigen {{candidateRows}}
Abgrenzung Waehrung Wert Diff. IC Diff. ohne IC
"""; } static string Amount(decimal? value) => value.HasValue ? value.Value.ToString("#,##0.00", CultureInfo.GetCultureInfo("de-CH")) : "-"; static string Html(string? value) => WebUtility.HtmlEncode(value ?? string.Empty); sealed class CheckedExcelReference { public string Label { get; set; } = string.Empty; public decimal? LocalCurrencyValue { get; set; } public decimal? ChfValue { get; set; } public decimal? PowerBiValue { get; set; } public string Status { get; set; } = string.Empty; }