Add DE finance rules and IIS path base
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Trafag Finanze/Sales Management Cockpit</title>
|
<title>Trafag Finanze/Sales Management Cockpit</title>
|
||||||
<base href="/" />
|
<base href="@BaseHref" />
|
||||||
<link href="css/app.css" rel="stylesheet" />
|
<link href="css/app.css" rel="stylesheet" />
|
||||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
|
||||||
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
||||||
@@ -17,3 +17,21 @@
|
|||||||
<script src="js/download.js"></script>
|
<script src="js/download.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Inject]
|
||||||
|
private IConfiguration Configuration { get; set; } = default!;
|
||||||
|
|
||||||
|
private string BaseHref
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var pathBase = Configuration["ASPNETCORE_PATHBASE"]?.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(pathBase) || pathBase == "/")
|
||||||
|
return "/";
|
||||||
|
|
||||||
|
pathBase = "/" + pathBase.Trim('/');
|
||||||
|
return $"{pathBase}/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -110,6 +110,11 @@ builder.Services.AddScoped<IHrKpiAccessService, HrKpiAccessService>();
|
|||||||
builder.Services.AddScoped<IFinanceCockpitAccessService, FinanceCockpitAccessService>();
|
builder.Services.AddScoped<IFinanceCockpitAccessService, FinanceCockpitAccessService>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
var pathBase = app.Configuration["ASPNETCORE_PATHBASE"];
|
||||||
|
if (!string.IsNullOrWhiteSpace(pathBase))
|
||||||
|
{
|
||||||
|
app.UsePathBase(pathBase.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
using (var scope = app.Services.CreateScope())
|
using (var scope = app.Services.CreateScope())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -760,7 +760,7 @@ public class DatabaseSeedService : IDatabaseSeedService
|
|||||||
new FinanceReference { Key = "CH", Label = "Trafag CH", Year = 2025 },
|
new FinanceReference { Key = "CH", Label = "Trafag CH", Year = 2025 },
|
||||||
new FinanceReference { Key = "CN", Label = "Trafag CN", Year = 2025 },
|
new FinanceReference { Key = "CN", Label = "Trafag CN", Year = 2025 },
|
||||||
new FinanceReference { Key = "CZ", Label = "Trafag CZ", Year = 2025, LocalCurrencyValue = 95458782m },
|
new FinanceReference { Key = "CZ", Label = "Trafag CZ", Year = 2025, LocalCurrencyValue = 95458782m },
|
||||||
new FinanceReference { Key = "DE", Label = "Trafag DE", Year = 2025, LocalCurrencyValue = 3635923m },
|
new FinanceReference { Key = "DE", Label = "Trafag DE", Year = 2025, LocalCurrencyValue = 3652394.46m },
|
||||||
new FinanceReference { Key = "ES", Label = "Trafag ES", Year = 2025, LocalCurrencyValue = 3102333.61m },
|
new FinanceReference { Key = "ES", Label = "Trafag ES", Year = 2025, LocalCurrencyValue = 3102333.61m },
|
||||||
new FinanceReference { Key = "FR", Label = "Trafag FR", Year = 2025, LocalCurrencyValue = 1450582m, CheckValue = 1471218m },
|
new FinanceReference { Key = "FR", Label = "Trafag FR", Year = 2025, LocalCurrencyValue = 1450582m, CheckValue = 1471218m },
|
||||||
new FinanceReference { Key = "GFS", Label = "Trafag GfS", Year = 2025, LocalCurrencyValue = 6495513m },
|
new FinanceReference { Key = "GFS", Label = "Trafag GfS", Year = 2025, LocalCurrencyValue = 6495513m },
|
||||||
@@ -803,6 +803,12 @@ public class DatabaseSeedService : IDatabaseSeedService
|
|||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (current.Key == "DE" && current.Year == 2025 && current.LocalCurrencyValue != 3652394.46m)
|
||||||
|
{
|
||||||
|
current.LocalCurrencyValue = 3652394.46m;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ namespace TrafagSalesExporter.Services;
|
|||||||
|
|
||||||
public class ExcelExportService : IExcelExportService
|
public class ExcelExportService : IExcelExportService
|
||||||
{
|
{
|
||||||
|
private const int GermanyAlphaplanFinanceYear = 2025;
|
||||||
|
|
||||||
public string CreateExcelFile(string outputDirectory, string tsc, DateTime fileDate, List<SalesRecord> records)
|
public string CreateExcelFile(string outputDirectory, string tsc, DateTime fileDate, List<SalesRecord> records)
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(outputDirectory);
|
Directory.CreateDirectory(outputDirectory);
|
||||||
@@ -132,12 +134,13 @@ public class ExcelExportService : IExcelExportService
|
|||||||
var financeDate = ResolveFinanceDate(record);
|
var financeDate = ResolveFinanceDate(record);
|
||||||
var financeCountryKey = ResolveFinanceCountryKey(record.Land, record.Tsc);
|
var financeCountryKey = ResolveFinanceCountryKey(record.Land, record.Tsc);
|
||||||
var financeInclude = ResolveFinanceInclude(record, financeCountryKey, italyBlankSupplierCountryRows);
|
var financeInclude = ResolveFinanceInclude(record, financeCountryKey, italyBlankSupplierCountryRows);
|
||||||
|
var financeNetSalesActual = ResolveFinanceNetSalesActual(record, financeCountryKey, financeInclude);
|
||||||
ws.Cell(row, 36).Value = financeDate.Year;
|
ws.Cell(row, 36).Value = financeDate.Year;
|
||||||
ws.Cell(row, 37).Value = financeCountryKey;
|
ws.Cell(row, 37).Value = financeCountryKey;
|
||||||
ws.Cell(row, 38).Value = financeDate.ToString("dd.MM.yyyy");
|
ws.Cell(row, 38).Value = financeDate.ToString("dd.MM.yyyy");
|
||||||
ws.Cell(row, 39).Value = financeInclude ? record.SalesPriceValue : 0m;
|
ws.Cell(row, 39).Value = financeNetSalesActual;
|
||||||
ws.Cell(row, 40).Value = ResolveFinanceCurrency(record);
|
ws.Cell(row, 40).Value = ResolveFinanceCurrency(record);
|
||||||
ws.Cell(row, 41).Value = financeInclude && record.SalesPriceValue != 0m ? "TRUE" : "FALSE";
|
ws.Cell(row, 41).Value = financeInclude && financeNetSalesActual != 0m ? "TRUE" : "FALSE";
|
||||||
ws.Cell(row, 42).Value = financeInclude
|
ws.Cell(row, 42).Value = financeInclude
|
||||||
? "Sales Price/Value"
|
? "Sales Price/Value"
|
||||||
: ResolveFinanceExclusionReason(record, financeCountryKey);
|
: ResolveFinanceExclusionReason(record, financeCountryKey);
|
||||||
@@ -186,14 +189,16 @@ public class ExcelExportService : IExcelExportService
|
|||||||
{
|
{
|
||||||
var financeDate = ResolveFinanceDate(record);
|
var financeDate = ResolveFinanceDate(record);
|
||||||
var countryKey = ResolveFinanceCountryKey(record.Land, record.Tsc);
|
var countryKey = ResolveFinanceCountryKey(record.Land, record.Tsc);
|
||||||
var include = ResolveFinanceInclude(record, countryKey, italyBlankSupplierCountryRows) && record.SalesPriceValue != 0m;
|
var rawInclude = ResolveFinanceInclude(record, countryKey, italyBlankSupplierCountryRows);
|
||||||
|
var value = ResolveFinanceNetSalesActual(record, countryKey, rawInclude);
|
||||||
|
var include = rawInclude && value != 0m;
|
||||||
return new
|
return new
|
||||||
{
|
{
|
||||||
Year = financeDate.Year,
|
Year = financeDate.Year,
|
||||||
CountryKey = countryKey,
|
CountryKey = countryKey,
|
||||||
Currency = ResolveFinanceCurrency(record),
|
Currency = ResolveFinanceCurrency(record),
|
||||||
Include = include,
|
Include = include,
|
||||||
Value = include ? record.SalesPriceValue : 0m
|
Value = value
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.GroupBy(row => new { row.Year, row.CountryKey, row.Currency })
|
.GroupBy(row => new { row.Year, row.CountryKey, row.Currency })
|
||||||
@@ -231,7 +236,7 @@ public class ExcelExportService : IExcelExportService
|
|||||||
private static string BuildFinanceSummaryHint(string countryKey)
|
private static string BuildFinanceSummaryHint(string countryKey)
|
||||||
=> countryKey.ToUpperInvariant() switch
|
=> countryKey.ToUpperInvariant() switch
|
||||||
{
|
{
|
||||||
"DE" => "DE Alphaplan ist technisch vorbereitet; Kundenlaender/Filter fachlich noch bestaetigen.",
|
"DE" => "DE Alphaplan Jahresfile 2025: Weiterberechnungen ausgeschlossen; GS negativ, GS2510095 2024.",
|
||||||
"IT" => "IT: Trafag Italia ausgeschlossen; doppelte Blank-Supplier-Zeilen nur einmal.",
|
"IT" => "IT: Trafag Italia ausgeschlossen; doppelte Blank-Supplier-Zeilen nur einmal.",
|
||||||
"UK" => "UK: Sage/Manual Excel, Credit Notes negativ.",
|
"UK" => "UK: Sage/Manual Excel, Credit Notes negativ.",
|
||||||
"ES" => "ES: Sage CSV/Manual Excel, REC/Credit Notes negativ.",
|
"ES" => "ES: Sage CSV/Manual Excel, REC/Credit Notes negativ.",
|
||||||
@@ -253,8 +258,9 @@ public class ExcelExportService : IExcelExportService
|
|||||||
("3. Gueltige Zeilen filtern", "Finance | Include = TRUE"),
|
("3. Gueltige Zeilen filtern", "Finance | Include = TRUE"),
|
||||||
("4. Summe bilden", "Finance | Net Sales Actual summieren"),
|
("4. Summe bilden", "Finance | Net Sales Actual summieren"),
|
||||||
("Waehrung", "Finance | Currency zeigt die fuer den Finance-Abgleich fuehrende Hauswaehrung."),
|
("Waehrung", "Finance | Currency zeigt die fuer den Finance-Abgleich fuehrende Hauswaehrung."),
|
||||||
("Datum", "Finance | Date verwendet PostingDate, danach InvoiceDate, danach ExtractionDate."),
|
("Datum", "Finance | Date verwendet PostingDate, danach InvoiceDate, danach ExtractionDate. DE Alphaplan wird als Jahresfile 2025 behandelt."),
|
||||||
("Wertquelle", "Finance | Source Value Field zeigt, aus welchem Rohfeld der Finance-Wert kommt."),
|
("Wertquelle", "Finance | Source Value Field zeigt, aus welchem Rohfeld der Finance-Wert kommt."),
|
||||||
|
("DE-Sonderregel", "Fuer DE gilt die Deutschland-Rueckmeldung: Trafag AG und Magnetic Sense ausgeschlossen, GS-Gutschriften negativ, GS2510095 nicht in 2025."),
|
||||||
("IT-Sonderregel", "Fuer IT wird Trafag Italia im Finance-Wert ausgeschlossen; doppelte IT-Zeilen ohne Supplier country werden nur einmal gezaehlt."),
|
("IT-Sonderregel", "Fuer IT wird Trafag Italia im Finance-Wert ausgeschlossen; doppelte IT-Zeilen ohne Supplier country werden nur einmal gezaehlt."),
|
||||||
("Nicht verwenden", "Nicht Land, TSC, Document Total LC oder andere Betragsspalten fuer den CFO-Abgleich erraten."),
|
("Nicht verwenden", "Nicht Land, TSC, Document Total LC oder andere Betragsspalten fuer den CFO-Abgleich erraten."),
|
||||||
("Hinweis", "Offene fachliche Differenzen bleiben sichtbar; diese Excel-Sicht soll die gleiche Ist-Summe wie das Testprogramm reproduzieren.")
|
("Hinweis", "Offene fachliche Differenzen bleiben sichtbar; diese Excel-Sicht soll die gleiche Ist-Summe wie das Testprogramm reproduzieren.")
|
||||||
@@ -293,7 +299,13 @@ public class ExcelExportService : IExcelExportService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static DateTime ResolveFinanceDate(SalesRecord record)
|
private static DateTime ResolveFinanceDate(SalesRecord record)
|
||||||
=> record.PostingDate ?? record.InvoiceDate ?? record.ExtractionDate;
|
{
|
||||||
|
var countryKey = ResolveFinanceCountryKey(record.Land, record.Tsc);
|
||||||
|
if (countryKey.Equals("DE", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return new DateTime(GermanyAlphaplanFinanceYear, 12, 31);
|
||||||
|
|
||||||
|
return record.PostingDate ?? record.InvoiceDate ?? record.ExtractionDate;
|
||||||
|
}
|
||||||
|
|
||||||
private static string ResolveFinanceCurrency(SalesRecord record)
|
private static string ResolveFinanceCurrency(SalesRecord record)
|
||||||
=> ResolveFinanceCountryKey(record.Land, record.Tsc) switch
|
=> ResolveFinanceCountryKey(record.Land, record.Tsc) switch
|
||||||
@@ -330,6 +342,9 @@ public class ExcelExportService : IExcelExportService
|
|||||||
|
|
||||||
private static bool ResolveFinanceInclude(SalesRecord record, string financeCountryKey, HashSet<string> italyBlankSupplierCountryRows)
|
private static bool ResolveFinanceInclude(SalesRecord record, string financeCountryKey, HashSet<string> italyBlankSupplierCountryRows)
|
||||||
{
|
{
|
||||||
|
if (financeCountryKey.Equals("DE", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return IsIncludedGermanyFinanceRow(record);
|
||||||
|
|
||||||
if (!financeCountryKey.Equals("IT", StringComparison.OrdinalIgnoreCase))
|
if (!financeCountryKey.Equals("IT", StringComparison.OrdinalIgnoreCase))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -350,9 +365,52 @@ public class ExcelExportService : IExcelExportService
|
|||||||
if (financeCountryKey.Equals("IT", StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace(record.SupplierCountry))
|
if (financeCountryKey.Equals("IT", StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace(record.SupplierCountry))
|
||||||
return "Excluded IT duplicate without Supplier country";
|
return "Excluded IT duplicate without Supplier country";
|
||||||
|
|
||||||
|
if (financeCountryKey.Equals("DE", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return ResolveGermanyExclusionReason(record);
|
||||||
|
|
||||||
return "Excluded";
|
return "Excluded";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static decimal ResolveFinanceNetSalesActual(SalesRecord record, string financeCountryKey, bool financeInclude)
|
||||||
|
{
|
||||||
|
if (!financeInclude)
|
||||||
|
return 0m;
|
||||||
|
|
||||||
|
if (financeCountryKey.Equals("DE", StringComparison.OrdinalIgnoreCase) && IsGermanyCreditNote(record))
|
||||||
|
return -Math.Abs(record.SalesPriceValue);
|
||||||
|
|
||||||
|
return record.SalesPriceValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsIncludedGermanyFinanceRow(SalesRecord record)
|
||||||
|
=> !IsGermanyTrafagAgRecharge(record) &&
|
||||||
|
!IsGermanyMagneticSenseRecharge(record) &&
|
||||||
|
!IsGermanyCreditNoteAlreadyCapturedInPriorYear(record);
|
||||||
|
|
||||||
|
private static string ResolveGermanyExclusionReason(SalesRecord record)
|
||||||
|
{
|
||||||
|
if (IsGermanyTrafagAgRecharge(record))
|
||||||
|
return "Excluded DE Weiterberechnung Trafag AG";
|
||||||
|
if (IsGermanyMagneticSenseRecharge(record))
|
||||||
|
return "Excluded DE Weiterberechnung Magnetic Sense";
|
||||||
|
if (IsGermanyCreditNoteAlreadyCapturedInPriorYear(record))
|
||||||
|
return "Excluded DE GS2510095 already captured in 2024";
|
||||||
|
|
||||||
|
return "Excluded DE";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsGermanyTrafagAgRecharge(SalesRecord record)
|
||||||
|
=> NormalizeFinanceText(record.CustomerName) == "TRAFAG AG";
|
||||||
|
|
||||||
|
private static bool IsGermanyMagneticSenseRecharge(SalesRecord record)
|
||||||
|
=> NormalizeFinanceText(record.CustomerName).Contains("MAGNETIC SENSE", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
private static bool IsGermanyCreditNote(SalesRecord record)
|
||||||
|
=> (record.InvoiceNumber ?? string.Empty).Trim().StartsWith("GS", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
private static bool IsGermanyCreditNoteAlreadyCapturedInPriorYear(SalesRecord record)
|
||||||
|
=> (record.InvoiceNumber ?? string.Empty).Trim().Equals("GS2510095", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
private static bool IsExcludedItalyCustomer(SalesRecord record)
|
private static bool IsExcludedItalyCustomer(SalesRecord record)
|
||||||
=> NormalizeFinanceText(record.CustomerName).Contains("TRAFAG ITALIA", StringComparison.OrdinalIgnoreCase);
|
=> NormalizeFinanceText(record.CustomerName).Contains("TRAFAG ITALIA", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
using ClosedXML.Excel;
|
||||||
|
using TrafagSalesExporter.Models;
|
||||||
|
using TrafagSalesExporter.Services;
|
||||||
|
|
||||||
|
namespace TrafagSalesExporter.Tests;
|
||||||
|
|
||||||
|
public class ExcelExportServiceTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void CreateConsolidatedExcelFile_Uses_Germany_Finance_Response_Rules()
|
||||||
|
{
|
||||||
|
var outputDirectory = Path.Combine(Path.GetTempPath(), $"trafag-export-{Guid.NewGuid():N}");
|
||||||
|
var service = new ExcelExportService();
|
||||||
|
var records = new List<SalesRecord>
|
||||||
|
{
|
||||||
|
CreateGermanyRecord("Normal GmbH", "Deutschland", "RE250001", 100m),
|
||||||
|
CreateGermanyRecord("Trafag AG", "Schweiz", "RE250002", 40m),
|
||||||
|
CreateGermanyRecord("Magnetic Sense GmbH", "Deutschland", "RE250003", 30m),
|
||||||
|
CreateGermanyRecord("Normal GmbH", "Deutschland", "GS2510096", 20m),
|
||||||
|
CreateGermanyRecord("Normal GmbH", "Deutschland", "GS2510095", 10m)
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var path = service.CreateConsolidatedExcelFile(outputDirectory, new DateTime(2026, 5, 20), records);
|
||||||
|
|
||||||
|
using var workbook = new XLWorkbook(path);
|
||||||
|
var summary = workbook.Worksheet("Finance Summary");
|
||||||
|
var deSummaryRow = summary.RowsUsed()
|
||||||
|
.Where(row => row.RowNumber() > 4)
|
||||||
|
.Single(row => row.Cell(1).GetValue<int>() == 2025 && row.Cell(2).GetString() == "DE");
|
||||||
|
|
||||||
|
Assert.Equal(2, deSummaryRow.Cell(4).GetValue<int>());
|
||||||
|
Assert.Equal(80m, deSummaryRow.Cell(5).GetValue<decimal>());
|
||||||
|
Assert.Equal(3, deSummaryRow.Cell(6).GetValue<int>());
|
||||||
|
|
||||||
|
var sales = workbook.Worksheet("Sales");
|
||||||
|
var includedGermanyRows = sales.RowsUsed()
|
||||||
|
.Skip(1)
|
||||||
|
.Where(row => row.Cell(36).GetValue<int>() == 2025)
|
||||||
|
.Where(row => row.Cell(37).GetString() == "DE")
|
||||||
|
.Where(row => row.Cell(41).GetString() == "TRUE")
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Assert.Equal(2, includedGermanyRows.Count);
|
||||||
|
Assert.Equal(80m, includedGermanyRows.Sum(row => row.Cell(39).GetValue<decimal>()));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (Directory.Exists(outputDirectory))
|
||||||
|
Directory.Delete(outputDirectory, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SalesRecord CreateGermanyRecord(string customerName, string customerCountry, string invoiceNumber, decimal value)
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
ExtractionDate = new DateTime(2026, 5, 20),
|
||||||
|
Tsc = "TRDE",
|
||||||
|
Land = "Deutschland",
|
||||||
|
InvoiceNumber = invoiceNumber,
|
||||||
|
PositionOnInvoice = 1,
|
||||||
|
CustomerName = customerName,
|
||||||
|
CustomerCountry = customerCountry,
|
||||||
|
SalesPriceValue = value,
|
||||||
|
SalesCurrency = "EUR",
|
||||||
|
CompanyCurrency = "EUR",
|
||||||
|
DocumentType = "Alphaplan Excel"
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
hostingModel="outofprocess">
|
hostingModel="outofprocess">
|
||||||
<environmentVariables>
|
<environmentVariables>
|
||||||
<environmentVariable name="ASPNETCORE_DETAILEDERRORS" value="true" />
|
<environmentVariable name="ASPNETCORE_DETAILEDERRORS" value="true" />
|
||||||
|
<environmentVariable name="ASPNETCORE_PATHBASE" value="/BiDashboard" />
|
||||||
</environmentVariables>
|
</environmentVariables>
|
||||||
</aspNetCore>
|
</aspNetCore>
|
||||||
</system.webServer>
|
</system.webServer>
|
||||||
|
|||||||
Reference in New Issue
Block a user