From 15dec06f31076ee618ce25251531e14f8dd7400f Mon Sep 17 00:00:00 2001 From: metacube Date: Mon, 4 May 2026 09:32:50 +0200 Subject: [PATCH] Add finance reconciliation probe --- TrafagSalesExporter/Program.cs | 1 + .../Services/DashboardPageService.cs | 10 +- .../Services/FinanceReconciliationService.cs | 307 +++++++++++++++ .../Tools/FinanceProbe/FinanceProbe.csproj | 12 + .../Tools/FinanceProbe/Program.cs | 364 ++++++++++++++++++ .../TrafagSalesExporter.csproj | 4 + TrafagSalesExporter/TrafagSalesExporter.sln | 19 + 7 files changed, 715 insertions(+), 2 deletions(-) create mode 100644 TrafagSalesExporter/Services/FinanceReconciliationService.cs create mode 100644 TrafagSalesExporter/Tools/FinanceProbe/FinanceProbe.csproj create mode 100644 TrafagSalesExporter/Tools/FinanceProbe/Program.cs diff --git a/TrafagSalesExporter/Program.cs b/TrafagSalesExporter/Program.cs index e8b85c5..30c95e0 100644 --- a/TrafagSalesExporter/Program.cs +++ b/TrafagSalesExporter/Program.cs @@ -74,6 +74,7 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/TrafagSalesExporter/Services/DashboardPageService.cs b/TrafagSalesExporter/Services/DashboardPageService.cs index cd60a41..93cc782 100644 --- a/TrafagSalesExporter/Services/DashboardPageService.cs +++ b/TrafagSalesExporter/Services/DashboardPageService.cs @@ -12,10 +12,14 @@ public interface IDashboardPageService public sealed class DashboardPageService : IDashboardPageService { private readonly IDbContextFactory _dbFactory; + private readonly IFinanceReconciliationService _financeReconciliationService; - public DashboardPageService(IDbContextFactory dbFactory) + public DashboardPageService( + IDbContextFactory dbFactory, + IFinanceReconciliationService financeReconciliationService) { _dbFactory = dbFactory; + _financeReconciliationService = financeReconciliationService; } public async Task LoadAsync() @@ -65,7 +69,8 @@ public sealed class DashboardPageService : IDashboardPageService return new DashboardPageState { DashboardRows = rows, - ConsolidatedRows = BuildConsolidatedRows(await db.ExportSettings.FirstOrDefaultAsync() ?? new()) + ConsolidatedRows = BuildConsolidatedRows(await db.ExportSettings.FirstOrDefaultAsync() ?? new()), + NetSalesReferenceRows = await _financeReconciliationService.BuildNetSalesReferenceRowsAsync(2025) }; } @@ -114,6 +119,7 @@ public sealed class DashboardPageState { public List DashboardRows { get; set; } = []; public List ConsolidatedRows { get; set; } = []; + public List NetSalesReferenceRows { get; set; } = []; } public sealed class DashboardRow diff --git a/TrafagSalesExporter/Services/FinanceReconciliationService.cs b/TrafagSalesExporter/Services/FinanceReconciliationService.cs new file mode 100644 index 0000000..5fbbbc3 --- /dev/null +++ b/TrafagSalesExporter/Services/FinanceReconciliationService.cs @@ -0,0 +1,307 @@ +using Microsoft.EntityFrameworkCore; +using TrafagSalesExporter.Data; + +namespace TrafagSalesExporter.Services; + +public interface IFinanceReconciliationService +{ + Task> BuildNetSalesReferenceRowsAsync(int year = 2025); +} + +public sealed class FinanceReconciliationService : IFinanceReconciliationService +{ + private readonly IDbContextFactory _dbFactory; + + private static readonly IReadOnlyList NetSalesReferences = + [ + new("AT", "Trafag AT", 3443863m, null), + new("CH", "Trafag CH", null, null), + new("CN", "Trafag CN", null, null), + new("CZ", "Trafag CZ", 95458782m, null), + new("DE", "Trafag DE", 3635923m, null), + new("ES", "Trafag ES", 3102334m, null), + new("FR", "Trafag FR", 1450582m, 1471218m), + new("GFS", "Trafag GfS", 6495513m, null), + new("IN", "Trafag IN", 747341702m, 750936591m), + new("IT", "Trafag IT", 7669840m, null), + new("JP", "Trafag JP", 187739814m, null), + new("MS", "Trafag MS", 1850199m, null), + new("MSA", "Trafag MSA", 1445258m, null), + new("PL", "Trafag PL Poltraf", 11279297m, null), + new("RU", "Rrafag RU", null, null), + new("UK", "Trafag UK", 3538972m, 3749865m), + new("US", "Traga US", 3896728m, 3749865m) + ]; + + public FinanceReconciliationService(IDbContextFactory dbFactory) + { + _dbFactory = dbFactory; + } + + public async Task> BuildNetSalesReferenceRowsAsync(int year = 2025) + { + await using var db = await _dbFactory.CreateDbContextAsync(); + + var centralRows = await db.CentralSalesRecords + .AsNoTracking() + .Where(r => (r.InvoiceDate ?? r.ExtractionDate).Year == year) + .Select(r => new NetSalesActualSourceRow( + r.Land, + r.Tsc, + r.DocumentEntry, + r.InvoiceNumber, + r.DocumentType, + r.CustomerNumber, + r.CustomerName, + r.SalesCurrency, + r.DocumentCurrency, + r.CompanyCurrency, + r.SalesPriceValue, + r.DocumentTotalForeignCurrency, + r.DocumentTotalLocalCurrency, + r.VatSumForeignCurrency, + r.VatSumLocalCurrency)) + .ToListAsync(); + + var groupedActuals = centralRows + .GroupBy(r => ResolveReferenceKey(r.Land, r.Tsc), StringComparer.OrdinalIgnoreCase) + .ToDictionary( + g => g.Key, + BuildNetSalesActual, + StringComparer.OrdinalIgnoreCase); + + var activeSiteKeys = (await db.Sites + .AsNoTracking() + .Where(s => s.IsActive) + .Select(s => new { s.Land, s.TSC }) + .ToListAsync()) + .Select(s => ResolveReferenceKey(s.Land, s.TSC)) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + return NetSalesReferences + .Where(reference => activeSiteKeys.Contains(reference.Key) || groupedActuals.ContainsKey(reference.Key)) + .Select(reference => + { + groupedActuals.TryGetValue(reference.Key, out var actual); + var referenceValue = reference.PowerBiValue ?? reference.LocalCurrencyValue; + var selected = actual is null || !referenceValue.HasValue + ? actual?.Candidates.FirstOrDefault() + : actual.Candidates + .OrderBy(candidate => Math.Abs(candidate.Value - referenceValue.Value)) + .FirstOrDefault(); + var difference = selected is null || !referenceValue.HasValue ? (decimal?)null : selected.Value - referenceValue.Value; + var intercompanyAdjustedDifference = selected is null || !referenceValue.HasValue + ? (decimal?)null + : selected.ValueExcludingIntercompany - referenceValue.Value; + + return new NetSalesReferenceRow + { + Key = reference.Key, + Label = reference.Label, + ActualValue = selected?.Value, + IntercompanyDeduction = selected?.IntercompanyValue, + ActualValueExcludingIntercompany = selected?.ValueExcludingIntercompany, + ReferenceValue = referenceValue, + Difference = difference, + DifferenceExcludingIntercompany = intercompanyAdjustedDifference, + RowCount = actual?.RowCount ?? 0, + Currencies = actual?.Currencies ?? string.Empty, + ValueField = selected?.Label ?? string.Empty, + ActualCurrency = selected?.Currency ?? string.Empty, + ReferenceSource = reference.PowerBiValue.HasValue ? "Power BI" : "LC", + ReferenceCurrency = reference.PowerBiValue.HasValue ? "Power BI Original" : "LC", + Status = BuildReferenceStatus(difference), + Candidates = actual?.Candidates.Select(candidate => new NetSalesCandidateRow + { + Key = candidate.Key, + Label = candidate.Label, + Currency = candidate.Currency, + Value = candidate.Value, + IntercompanyValue = candidate.IntercompanyValue, + ValueExcludingIntercompany = candidate.ValueExcludingIntercompany, + Difference = referenceValue.HasValue ? candidate.Value - referenceValue.Value : null, + DifferenceExcludingIntercompany = referenceValue.HasValue + ? candidate.ValueExcludingIntercompany - referenceValue.Value + : null + }).ToList() ?? [] + }; + }) + .OrderBy(row => row.Label, StringComparer.OrdinalIgnoreCase) + .ToList(); + } + + private static NetSalesActual BuildNetSalesActual(IEnumerable rows) + { + var rowList = rows.ToList(); + var documentRows = rowList + .GroupBy(row => BuildDocumentKey(row.Tsc, row.DocumentType, row.DocumentEntry, row.InvoiceNumber), StringComparer.OrdinalIgnoreCase) + .Select(g => g.First()) + .ToList(); + + var candidates = new List + { + new( + "SalesPriceValue", + "Sales Price/Value", + ResolveCurrencyLabel(rowList.Select(row => row.SalesCurrency)), + rowList.Sum(row => row.SalesPriceValue), + rowList.Where(IsLikelyIntercompanyCustomer).Sum(row => row.SalesPriceValue)) + }; + + var netDocumentForeignCurrency = documentRows.Sum(row => row.DocumentTotalForeignCurrency - row.VatSumForeignCurrency); + if (netDocumentForeignCurrency != 0m) + candidates.Add(new( + "NetDocumentForeignCurrency", + "DocTotalFC - VatSumFC", + ResolveCurrencyLabel(rowList.Select(row => row.DocumentCurrency)), + netDocumentForeignCurrency, + documentRows.Where(IsLikelyIntercompanyCustomer).Sum(row => row.DocumentTotalForeignCurrency - row.VatSumForeignCurrency))); + + var netDocumentLocalCurrency = documentRows.Sum(row => row.DocumentTotalLocalCurrency - row.VatSumLocalCurrency); + if (netDocumentLocalCurrency != 0m) + candidates.Add(new( + "NetDocumentLocalCurrency", + "DocTotal - VatSum", + ResolveCurrencyLabel(rowList.Select(row => row.CompanyCurrency)), + netDocumentLocalCurrency, + documentRows.Where(IsLikelyIntercompanyCustomer).Sum(row => row.DocumentTotalLocalCurrency - row.VatSumLocalCurrency))); + + return new NetSalesActual + { + RowCount = rowList.Count, + Currencies = string.Join(", ", rowList.Select(row => row.SalesCurrency) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .OrderBy(x => x, StringComparer.OrdinalIgnoreCase)), + Candidates = candidates + }; + } + + private static string ResolveCurrencyLabel(IEnumerable currencies) + { + var distinct = currencies + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => x.Trim().ToUpperInvariant()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .OrderBy(x => x, StringComparer.OrdinalIgnoreCase) + .ToList(); + + return distinct.Count == 0 ? "-" : string.Join(", ", distinct); + } + + private static string BuildDocumentKey(string tsc, string documentType, int documentEntry, string invoiceNumber) + => documentEntry > 0 + ? $"{tsc}|{documentType}|{documentEntry}" + : $"{tsc}|{documentType}|{invoiceNumber}"; + + private static bool IsLikelyIntercompanyCustomer(NetSalesActualSourceRow row) + { + var customerNumber = row.CustomerNumber?.Trim() ?? string.Empty; + var customerName = row.CustomerName?.Trim() ?? string.Empty; + + if (string.IsNullOrWhiteSpace(customerNumber) && string.IsNullOrWhiteSpace(customerName)) + return false; + + if (row.Tsc.Equals("TRIT", StringComparison.OrdinalIgnoreCase)) + { + return customerNumber.Equals("C_IT01_0306794", StringComparison.OrdinalIgnoreCase) || + customerNumber.Equals("C_CH01_0302179", StringComparison.OrdinalIgnoreCase) || + customerName.Equals("TRAFAG ITALIA S.R.L.", StringComparison.OrdinalIgnoreCase) || + customerName.Equals("Trafag AG", StringComparison.OrdinalIgnoreCase); + } + + return false; + } + + private static string BuildReferenceStatus(decimal? difference) + { + if (!difference.HasValue) + return "Keine Daten"; + + return Math.Abs(difference.Value) <= 1m ? "OK" : "Pruefen"; + } + + private static string ResolveReferenceKey(string land, string tsc) + { + var normalizedLand = (land ?? string.Empty).Trim().ToUpperInvariant(); + var normalizedTsc = (tsc ?? string.Empty).Trim().ToUpperInvariant(); + + if (normalizedLand.Contains("FRANK") || normalizedTsc.Contains("FR")) return "FR"; + if (normalizedLand.Contains("IND") || normalizedTsc.Contains("IN")) return "IN"; + if (normalizedLand.Contains("ITAL") || normalizedTsc.Contains("IT")) return "IT"; + if (normalizedLand.Contains("ENGL") || normalizedLand.Contains("KINGDOM") || normalizedTsc.Contains("UK") || normalizedTsc.Contains("GB")) return "UK"; + if (normalizedLand.Contains("USA") || normalizedLand.Contains("UNITED STATES") || normalizedTsc.Contains("US")) return "US"; + if (normalizedLand.Contains("DEUT") || normalizedTsc.Contains("DE")) return "DE"; + if (normalizedLand.Contains("SPAN") || normalizedTsc is "SE" or "ES") return "ES"; + if (normalizedLand.Contains("SCHWE") || normalizedTsc.Contains("CH")) return "CH"; + + return normalizedTsc.Replace("TR", string.Empty); + } +} + +public sealed class NetSalesReferenceRow +{ + public string Key { get; set; } = string.Empty; + public string Label { get; set; } = string.Empty; + public decimal? ActualValue { get; set; } + public decimal? IntercompanyDeduction { get; set; } + public decimal? ActualValueExcludingIntercompany { get; set; } + public decimal? ReferenceValue { get; set; } + public decimal? Difference { get; set; } + public decimal? DifferenceExcludingIntercompany { get; set; } + public int RowCount { get; set; } + public string Currencies { get; set; } = string.Empty; + public string ValueField { get; set; } = string.Empty; + public string ActualCurrency { get; set; } = string.Empty; + public string ReferenceSource { get; set; } = string.Empty; + public string ReferenceCurrency { get; set; } = string.Empty; + public string Status { get; set; } = string.Empty; + public List Candidates { get; set; } = []; +} + +public sealed class NetSalesCandidateRow +{ + public string Key { get; set; } = string.Empty; + public string Label { get; set; } = string.Empty; + public string Currency { get; set; } = string.Empty; + public decimal Value { get; set; } + public decimal IntercompanyValue { get; set; } + public decimal ValueExcludingIntercompany { get; set; } + public decimal? Difference { get; set; } + public decimal? DifferenceExcludingIntercompany { get; set; } +} + +internal sealed record NetSalesReferenceDefinition( + string Key, + string Label, + decimal? LocalCurrencyValue, + decimal? PowerBiValue); + +internal sealed class NetSalesActual +{ + public int RowCount { get; set; } + public string Currencies { get; set; } = string.Empty; + public List Candidates { get; set; } = []; +} + +internal sealed record NetSalesActualSourceRow( + string Land, + string Tsc, + int DocumentEntry, + string InvoiceNumber, + string DocumentType, + string CustomerNumber, + string CustomerName, + string SalesCurrency, + string DocumentCurrency, + string CompanyCurrency, + decimal SalesPriceValue, + decimal DocumentTotalForeignCurrency, + decimal DocumentTotalLocalCurrency, + decimal VatSumForeignCurrency, + decimal VatSumLocalCurrency); + +internal sealed record NetSalesCandidate(string Key, string Label, string Currency, decimal Value, decimal IntercompanyValue) +{ + public decimal ValueExcludingIntercompany => Value - IntercompanyValue; +} diff --git a/TrafagSalesExporter/Tools/FinanceProbe/FinanceProbe.csproj b/TrafagSalesExporter/Tools/FinanceProbe/FinanceProbe.csproj new file mode 100644 index 0000000..d5645a5 --- /dev/null +++ b/TrafagSalesExporter/Tools/FinanceProbe/FinanceProbe.csproj @@ -0,0 +1,12 @@ + + + net8.0 + enable + enable + x64 + + + + + + diff --git a/TrafagSalesExporter/Tools/FinanceProbe/Program.cs b/TrafagSalesExporter/Tools/FinanceProbe/Program.cs new file mode 100644 index 0000000..506b4fb --- /dev/null +++ b/TrafagSalesExporter/Tools/FinanceProbe/Program.cs @@ -0,0 +1,364 @@ +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)))}} + +
StatusFirmaGewaehlte AbgrenzungIst-WaehrungIst 2025Referenz-WaehrungReferenzExcel LCExcel CHFExcel Power BIExcel StatusDifferenzOhne IC Diff.WaehrungZeilenVarianten
+
+
+ + +"""; +} + +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}} +
AbgrenzungWaehrungWertDiff.ICDiff. 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; +} diff --git a/TrafagSalesExporter/TrafagSalesExporter.csproj b/TrafagSalesExporter/TrafagSalesExporter.csproj index 1ee37af..de73403 100644 --- a/TrafagSalesExporter/TrafagSalesExporter.csproj +++ b/TrafagSalesExporter/TrafagSalesExporter.csproj @@ -35,9 +35,13 @@ + + + + diff --git a/TrafagSalesExporter/TrafagSalesExporter.sln b/TrafagSalesExporter/TrafagSalesExporter.sln index bfaccbf..c2ec35d 100644 --- a/TrafagSalesExporter/TrafagSalesExporter.sln +++ b/TrafagSalesExporter/TrafagSalesExporter.sln @@ -7,6 +7,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrafagSalesExporter", "Traf EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrafagSalesExporter.Tests", "TrafagSalesExporter.Tests\TrafagSalesExporter.Tests.csproj", "{A72D1AFB-E49E-4920-B783-EFFE1132435D}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{07C2787E-EAC7-C090-1BA3-A61EC2A24D84}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinanceProbe", "Tools\FinanceProbe\FinanceProbe.csproj", "{D7259652-1433-4EE4-A30C-DEFA623E7C10}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,10 +45,25 @@ Global {A72D1AFB-E49E-4920-B783-EFFE1132435D}.Release|x64.Build.0 = Release|Any CPU {A72D1AFB-E49E-4920-B783-EFFE1132435D}.Release|x86.ActiveCfg = Release|Any CPU {A72D1AFB-E49E-4920-B783-EFFE1132435D}.Release|x86.Build.0 = Release|Any CPU + {D7259652-1433-4EE4-A30C-DEFA623E7C10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7259652-1433-4EE4-A30C-DEFA623E7C10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7259652-1433-4EE4-A30C-DEFA623E7C10}.Debug|x64.ActiveCfg = Debug|Any CPU + {D7259652-1433-4EE4-A30C-DEFA623E7C10}.Debug|x64.Build.0 = Debug|Any CPU + {D7259652-1433-4EE4-A30C-DEFA623E7C10}.Debug|x86.ActiveCfg = Debug|Any CPU + {D7259652-1433-4EE4-A30C-DEFA623E7C10}.Debug|x86.Build.0 = Debug|Any CPU + {D7259652-1433-4EE4-A30C-DEFA623E7C10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7259652-1433-4EE4-A30C-DEFA623E7C10}.Release|Any CPU.Build.0 = Release|Any CPU + {D7259652-1433-4EE4-A30C-DEFA623E7C10}.Release|x64.ActiveCfg = Release|Any CPU + {D7259652-1433-4EE4-A30C-DEFA623E7C10}.Release|x64.Build.0 = Release|Any CPU + {D7259652-1433-4EE4-A30C-DEFA623E7C10}.Release|x86.ActiveCfg = Release|Any CPU + {D7259652-1433-4EE4-A30C-DEFA623E7C10}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {D7259652-1433-4EE4-A30C-DEFA623E7C10} = {07C2787E-EAC7-C090-1BA3-A61EC2A24D84} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DC174EA0-ECCB-4957-9D97-E7ABED992867} EndGlobalSection