diff --git a/TrafagSalesExporter/Components/Pages/Settings.razor b/TrafagSalesExporter/Components/Pages/Settings.razor index 4c5c4cb..8db9332 100644 --- a/TrafagSalesExporter/Components/Pages/Settings.razor +++ b/TrafagSalesExporter/Components/Pages/Settings.razor @@ -313,8 +313,8 @@ - @T("Schreibt beim Laenderexport je Standort eine Sales_*.csv mit den transformierten Daten.", - "Writes one Sales_*.csv per site during country export with the transformed data.") + @T("Schreibt beim Laenderexport je Standort eine Sales_ProcessedMergeInput_*.csv mit den transformierten Daten.", + "Writes one Sales_ProcessedMergeInput_*.csv per site during country export with the transformed data.") @@ -351,6 +351,7 @@ @T("Beispiel:", "Example:") Sales_TRFR_@(DateTime.Now.ToString("yyyy-MM-dd")).xlsx + / Sales_ProcessedMergeInput_TRFR_@(DateTime.Now.ToString("yyyy-MM-dd")).csv diff --git a/TrafagSalesExporter/Services/CentralSalesDataProvider.cs b/TrafagSalesExporter/Services/CentralSalesDataProvider.cs index dcfb23a..7ebb099 100644 --- a/TrafagSalesExporter/Services/CentralSalesDataProvider.cs +++ b/TrafagSalesExporter/Services/CentralSalesDataProvider.cs @@ -38,7 +38,7 @@ public sealed class CentralSalesDataProvider : ICentralSalesDataProvider { var directory = _auditCsvService.ResolveAuditCsvDirectory(settings); throw new InvalidOperationException( - $"Audit-CSV ist als zentrale Quelle aktiv, aber im Ordner '{directory}' wurden keine Sales_*.csv-Dateien gefunden."); + $"Audit-CSV ist als zentrale Quelle aktiv, aber im Ordner '{directory}' wurden keine Sales_ProcessedMergeInput_*.csv-Dateien gefunden."); } return records diff --git a/TrafagSalesExporter/Services/ExportAuditCsvService.cs b/TrafagSalesExporter/Services/ExportAuditCsvService.cs index 05c5137..49a94fd 100644 --- a/TrafagSalesExporter/Services/ExportAuditCsvService.cs +++ b/TrafagSalesExporter/Services/ExportAuditCsvService.cs @@ -21,6 +21,8 @@ public interface IExportAuditCsvService public sealed class ExportAuditCsvService : IExportAuditCsvService { private const char Delimiter = ';'; + private const string ProcessedMergeInputFilePrefix = "Sales_ProcessedMergeInput_"; + private const string LegacyFilePrefix = "Sales_"; private static readonly string[] Headers = [ @@ -84,7 +86,7 @@ public sealed class ExportAuditCsvService : IExportAuditCsvService Directory.CreateDirectory(directory); var tsc = string.IsNullOrWhiteSpace(site.TSC) ? "UNKNOWN" : site.TSC.Trim(); - var fileName = $"Sales_{SanitizeFileNamePart(tsc)}_{DateTime.UtcNow:yyyy-MM-dd}.csv"; + var fileName = $"{ProcessedMergeInputFilePrefix}{SanitizeFileNamePart(tsc)}_{DateTime.UtcNow:yyyy-MM-dd}.csv"; var path = Path.Combine(directory, fileName); await using var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read); @@ -105,12 +107,16 @@ public sealed class ExportAuditCsvService : IExportAuditCsvService if (!Directory.Exists(directory)) return []; - var latestFiles = Directory.EnumerateFiles(directory, "Sales_*.csv", SearchOption.TopDirectoryOnly) - .GroupBy(ResolveTscFromFileName, StringComparer.OrdinalIgnoreCase) + var latestFiles = EnumerateAuditCsvFiles(directory) + .Select(path => new { Path = path, Tsc = ResolveTscFromFileName(path) }) + .Where(file => !string.IsNullOrWhiteSpace(file.Tsc)) + .GroupBy(file => file.Tsc, StringComparer.OrdinalIgnoreCase) .Select(group => group - .OrderByDescending(File.GetLastWriteTimeUtc) - .ThenByDescending(Path.GetFileName, StringComparer.OrdinalIgnoreCase) - .First()) + .OrderByDescending(file => File.GetLastWriteTimeUtc(file.Path)) + .ThenByDescending(file => IsProcessedMergeInputFile(file.Path)) + .ThenByDescending(file => Path.GetFileName(file.Path), StringComparer.OrdinalIgnoreCase) + .First() + .Path) .OrderBy(path => path, StringComparer.OrdinalIgnoreCase) .ToList(); @@ -257,14 +263,29 @@ public sealed class ExportAuditCsvService : IExportAuditCsvService private static string ResolveTscFromFileName(string path) { var name = Path.GetFileNameWithoutExtension(path); - if (!name.StartsWith("Sales_", StringComparison.OrdinalIgnoreCase)) - return name; + if (name.StartsWith(ProcessedMergeInputFilePrefix, StringComparison.OrdinalIgnoreCase)) + return ResolveTscFromSuffix(name[ProcessedMergeInputFilePrefix.Length..]); - var withoutPrefix = name["Sales_".Length..]; - var lastUnderscore = withoutPrefix.LastIndexOf('_'); - return lastUnderscore <= 0 ? withoutPrefix : withoutPrefix[..lastUnderscore]; + if (name.StartsWith(LegacyFilePrefix, StringComparison.OrdinalIgnoreCase)) + return ResolveTscFromSuffix(name[LegacyFilePrefix.Length..]); + + return string.Empty; } + private static string ResolveTscFromSuffix(string suffix) + { + var lastUnderscore = suffix.LastIndexOf('_'); + return lastUnderscore <= 0 ? suffix : suffix[..lastUnderscore]; + } + + private static IEnumerable EnumerateAuditCsvFiles(string directory) + => Directory.EnumerateFiles(directory, $"{ProcessedMergeInputFilePrefix}*.csv", SearchOption.TopDirectoryOnly) + .Concat(Directory.EnumerateFiles(directory, $"{LegacyFilePrefix}*.csv", SearchOption.TopDirectoryOnly) + .Where(path => !IsProcessedMergeInputFile(path))); + + private static bool IsProcessedMergeInputFile(string path) + => Path.GetFileName(path).StartsWith(ProcessedMergeInputFilePrefix, StringComparison.OrdinalIgnoreCase); + private static string SanitizeFileNamePart(string value) { var invalid = Path.GetInvalidFileNameChars(); diff --git a/TrafagSalesExporter/Services/UiTextService.cs b/TrafagSalesExporter/Services/UiTextService.cs index 9654b5f..bcabb6d 100644 --- a/TrafagSalesExporter/Services/UiTextService.cs +++ b/TrafagSalesExporter/Services/UiTextService.cs @@ -254,7 +254,8 @@ public sealed class UiTextService : IUiTextService ["Transformer Ansicht"] = "Vista de transformaciones", ["Transformationscode"] = "Código de transformación", ["Keine Beschreibung."] = "Sin descripción.", - ["Optionales Argument."] = "Argumento opcional." + ["Optionales Argument."] = "Argumento opcional.", + ["Schreibt beim Laenderexport je Standort eine Sales_ProcessedMergeInput_*.csv mit den transformierten Daten."] = "Escribe una Sales_ProcessedMergeInput_*.csv por sitio durante la exportacion de paises con los datos transformados." }, ["it"] = new Dictionary(StringComparer.OrdinalIgnoreCase) { @@ -487,7 +488,8 @@ public sealed class UiTextService : IUiTextService ["Transformer Ansicht"] = "Vista trasformazioni", ["Transformationscode"] = "Codice trasformazione", ["Keine Beschreibung."] = "Nessuna descrizione.", - ["Optionales Argument."] = "Argomento opzionale." + ["Optionales Argument."] = "Argomento opzionale.", + ["Schreibt beim Laenderexport je Standort eine Sales_ProcessedMergeInput_*.csv mit den transformierten Daten."] = "Scrive una Sales_ProcessedMergeInput_*.csv per sito durante l'esportazione dei Paesi con i dati trasformati." }, ["hi"] = new Dictionary(StringComparer.OrdinalIgnoreCase) { @@ -720,7 +722,8 @@ public sealed class UiTextService : IUiTextService ["Transformer Ansicht"] = "रूपांतरण दृश्य", ["Transformationscode"] = "रूपांतरण कोड", ["Keine Beschreibung."] = "कोई विवरण नहीं.", - ["Optionales Argument."] = "वैकल्पिक तर्क." + ["Optionales Argument."] = "वैकल्पिक तर्क.", + ["Schreibt beim Laenderexport je Standort eine Sales_ProcessedMergeInput_*.csv mit den transformierten Daten."] = "Writes one Sales_ProcessedMergeInput_*.csv per site during country export with the transformed data." } }; diff --git a/TrafagSalesExporter/TrafagSalesExporter.Tests/ExportAuditCsvServiceTests.cs b/TrafagSalesExporter/TrafagSalesExporter.Tests/ExportAuditCsvServiceTests.cs index 862d15c..7ed6c80 100644 --- a/TrafagSalesExporter/TrafagSalesExporter.Tests/ExportAuditCsvServiceTests.cs +++ b/TrafagSalesExporter/TrafagSalesExporter.Tests/ExportAuditCsvServiceTests.cs @@ -67,6 +67,7 @@ public sealed class ExportAuditCsvServiceTests : IDisposable Assert.True(File.Exists(path)); Assert.Equal(_tempDirectory, Path.GetDirectoryName(path)); + Assert.StartsWith("Sales_ProcessedMergeInput_TRCH_", Path.GetFileName(path), StringComparison.OrdinalIgnoreCase); var records = await service.ReadLatestSiteAuditCsvRecordsAsync(settings); var roundtrip = Assert.Single(records); Assert.Equal("SAP", roundtrip.SourceSystem); @@ -80,6 +81,62 @@ public sealed class ExportAuditCsvServiceTests : IDisposable Assert.Equal(new DateTime(2026, 6, 11), roundtrip.InvoiceDate); } + [Fact] + public async Task ReadLatestSiteAuditCsvRecordsAsync_Reads_New_Name_Before_Legacy_Name() + { + var service = new ExportAuditCsvService(); + var settings = new ExportSettings + { + AuditCsvEnabled = true, + LocalSiteExportFolder = _tempDirectory + }; + var site = new Site { TSC = "TRSE", Land = "Spanien" }; + + var legacyPath = await service.WriteSiteAuditCsvAsync( + site, + settings, + "MANUAL_EXCEL", + _tempDirectory, + [ + new SalesRecord + { + SourceSystem = "MANUAL_EXCEL", + ExtractionDate = new DateTime(2026, 6, 10), + Tsc = "TRSE", + Land = "Spanien", + InvoiceNumber = "NEW", + SalesPriceValue = 20m + } + ]); + var oldPath = Path.Combine(_tempDirectory, "Sales_TRSE_2026-06-10.csv"); + File.Move(legacyPath!, oldPath); + File.SetLastWriteTimeUtc(oldPath, new DateTime(2026, 6, 10, 8, 0, 0, DateTimeKind.Utc)); + + var newPath = await service.WriteSiteAuditCsvAsync( + site, + settings, + "MANUAL_EXCEL", + _tempDirectory, + [ + new SalesRecord + { + SourceSystem = "MANUAL_EXCEL", + ExtractionDate = new DateTime(2026, 6, 11), + Tsc = "TRSE", + Land = "Spanien", + InvoiceNumber = "PROCESSED", + SalesPriceValue = 30m + } + ]); + File.SetLastWriteTimeUtc(newPath!, new DateTime(2026, 6, 11, 8, 0, 0, DateTimeKind.Utc)); + + var records = await service.ReadLatestSiteAuditCsvRecordsAsync(settings); + + var record = Assert.Single(records); + Assert.Equal("PROCESSED", record.InvoiceNumber); + Assert.Equal(30m, record.SalesPriceValue); + } + [Fact] public async Task CentralSalesDataProvider_Uses_AuditCsv_When_Configured() { diff --git a/TrafagSalesExporter/TrafagSalesExporter.Tests/SiteExportServiceTests.cs b/TrafagSalesExporter/TrafagSalesExporter.Tests/SiteExportServiceTests.cs index 112c569..fb3e176 100644 --- a/TrafagSalesExporter/TrafagSalesExporter.Tests/SiteExportServiceTests.cs +++ b/TrafagSalesExporter/TrafagSalesExporter.Tests/SiteExportServiceTests.cs @@ -101,7 +101,7 @@ public sealed class SiteExportServiceTests : IDisposable Assert.NotNull(result.FilePath); Assert.True(File.Exists(result.FilePath)); - var auditCsv = Directory.GetFiles(_tempDirectory, "Sales_TRSE_*.csv").Single(); + var auditCsv = Directory.GetFiles(_tempDirectory, "Sales_ProcessedMergeInput_TRSE_*.csv").Single(); Assert.True(File.Exists(auditCsv)); Assert.Equal(2, sharePoint.Uploads.Count);