Clarify processed merge audit CSV filenames
This commit is contained in:
@@ -313,8 +313,8 @@
|
|||||||
<MudItem xs="12" md="4">
|
<MudItem xs="12" md="4">
|
||||||
<MudSwitch @bind-Value="_exportSettings.AuditCsvEnabled" Label="@T("Audit-CSV je Standort schreiben", "Write audit CSV per site")" Color="Color.Primary" />
|
<MudSwitch @bind-Value="_exportSettings.AuditCsvEnabled" Label="@T("Audit-CSV je Standort schreiben", "Write audit CSV per site")" Color="Color.Primary" />
|
||||||
<MudText Typo="Typo.caption">
|
<MudText Typo="Typo.caption">
|
||||||
@T("Schreibt beim Laenderexport je Standort eine Sales_*.csv mit den transformierten Daten.",
|
@T("Schreibt beim Laenderexport je Standort eine Sales_ProcessedMergeInput_*.csv mit den transformierten Daten.",
|
||||||
"Writes one Sales_*.csv per site during country export with the transformed data.")
|
"Writes one Sales_ProcessedMergeInput_*.csv per site during country export with the transformed data.")
|
||||||
</MudText>
|
</MudText>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" md="4">
|
<MudItem xs="12" md="4">
|
||||||
@@ -351,6 +351,7 @@
|
|||||||
</MudText>
|
</MudText>
|
||||||
<MudText Typo="Typo.caption" Class="mt-1">
|
<MudText Typo="Typo.caption" Class="mt-1">
|
||||||
@T("Beispiel:", "Example:") Sales_TRFR_@(DateTime.Now.ToString("yyyy-MM-dd")).xlsx
|
@T("Beispiel:", "Example:") Sales_TRFR_@(DateTime.Now.ToString("yyyy-MM-dd")).xlsx
|
||||||
|
/ Sales_ProcessedMergeInput_TRFR_@(DateTime.Now.ToString("yyyy-MM-dd")).csv
|
||||||
</MudText>
|
</MudText>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public sealed class CentralSalesDataProvider : ICentralSalesDataProvider
|
|||||||
{
|
{
|
||||||
var directory = _auditCsvService.ResolveAuditCsvDirectory(settings);
|
var directory = _auditCsvService.ResolveAuditCsvDirectory(settings);
|
||||||
throw new InvalidOperationException(
|
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
|
return records
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ public interface IExportAuditCsvService
|
|||||||
public sealed class ExportAuditCsvService : IExportAuditCsvService
|
public sealed class ExportAuditCsvService : IExportAuditCsvService
|
||||||
{
|
{
|
||||||
private const char Delimiter = ';';
|
private const char Delimiter = ';';
|
||||||
|
private const string ProcessedMergeInputFilePrefix = "Sales_ProcessedMergeInput_";
|
||||||
|
private const string LegacyFilePrefix = "Sales_";
|
||||||
|
|
||||||
private static readonly string[] Headers =
|
private static readonly string[] Headers =
|
||||||
[
|
[
|
||||||
@@ -84,7 +86,7 @@ public sealed class ExportAuditCsvService : IExportAuditCsvService
|
|||||||
Directory.CreateDirectory(directory);
|
Directory.CreateDirectory(directory);
|
||||||
|
|
||||||
var tsc = string.IsNullOrWhiteSpace(site.TSC) ? "UNKNOWN" : site.TSC.Trim();
|
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);
|
var path = Path.Combine(directory, fileName);
|
||||||
|
|
||||||
await using var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
|
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))
|
if (!Directory.Exists(directory))
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
var latestFiles = Directory.EnumerateFiles(directory, "Sales_*.csv", SearchOption.TopDirectoryOnly)
|
var latestFiles = EnumerateAuditCsvFiles(directory)
|
||||||
.GroupBy(ResolveTscFromFileName, StringComparer.OrdinalIgnoreCase)
|
.Select(path => new { Path = path, Tsc = ResolveTscFromFileName(path) })
|
||||||
|
.Where(file => !string.IsNullOrWhiteSpace(file.Tsc))
|
||||||
|
.GroupBy(file => file.Tsc, StringComparer.OrdinalIgnoreCase)
|
||||||
.Select(group => group
|
.Select(group => group
|
||||||
.OrderByDescending(File.GetLastWriteTimeUtc)
|
.OrderByDescending(file => File.GetLastWriteTimeUtc(file.Path))
|
||||||
.ThenByDescending(Path.GetFileName, StringComparer.OrdinalIgnoreCase)
|
.ThenByDescending(file => IsProcessedMergeInputFile(file.Path))
|
||||||
.First())
|
.ThenByDescending(file => Path.GetFileName(file.Path), StringComparer.OrdinalIgnoreCase)
|
||||||
|
.First()
|
||||||
|
.Path)
|
||||||
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
|
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
@@ -257,14 +263,29 @@ public sealed class ExportAuditCsvService : IExportAuditCsvService
|
|||||||
private static string ResolveTscFromFileName(string path)
|
private static string ResolveTscFromFileName(string path)
|
||||||
{
|
{
|
||||||
var name = Path.GetFileNameWithoutExtension(path);
|
var name = Path.GetFileNameWithoutExtension(path);
|
||||||
if (!name.StartsWith("Sales_", StringComparison.OrdinalIgnoreCase))
|
if (name.StartsWith(ProcessedMergeInputFilePrefix, StringComparison.OrdinalIgnoreCase))
|
||||||
return name;
|
return ResolveTscFromSuffix(name[ProcessedMergeInputFilePrefix.Length..]);
|
||||||
|
|
||||||
var withoutPrefix = name["Sales_".Length..];
|
if (name.StartsWith(LegacyFilePrefix, StringComparison.OrdinalIgnoreCase))
|
||||||
var lastUnderscore = withoutPrefix.LastIndexOf('_');
|
return ResolveTscFromSuffix(name[LegacyFilePrefix.Length..]);
|
||||||
return lastUnderscore <= 0 ? withoutPrefix : withoutPrefix[..lastUnderscore];
|
|
||||||
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string ResolveTscFromSuffix(string suffix)
|
||||||
|
{
|
||||||
|
var lastUnderscore = suffix.LastIndexOf('_');
|
||||||
|
return lastUnderscore <= 0 ? suffix : suffix[..lastUnderscore];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> 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)
|
private static string SanitizeFileNamePart(string value)
|
||||||
{
|
{
|
||||||
var invalid = Path.GetInvalidFileNameChars();
|
var invalid = Path.GetInvalidFileNameChars();
|
||||||
|
|||||||
@@ -254,7 +254,8 @@ public sealed class UiTextService : IUiTextService
|
|||||||
["Transformer Ansicht"] = "Vista de transformaciones",
|
["Transformer Ansicht"] = "Vista de transformaciones",
|
||||||
["Transformationscode"] = "Código de transformación",
|
["Transformationscode"] = "Código de transformación",
|
||||||
["Keine Beschreibung."] = "Sin descripció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<string, string>(StringComparer.OrdinalIgnoreCase)
|
["it"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
@@ -487,7 +488,8 @@ public sealed class UiTextService : IUiTextService
|
|||||||
["Transformer Ansicht"] = "Vista trasformazioni",
|
["Transformer Ansicht"] = "Vista trasformazioni",
|
||||||
["Transformationscode"] = "Codice trasformazione",
|
["Transformationscode"] = "Codice trasformazione",
|
||||||
["Keine Beschreibung."] = "Nessuna descrizione.",
|
["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<string, string>(StringComparer.OrdinalIgnoreCase)
|
["hi"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
@@ -720,7 +722,8 @@ public sealed class UiTextService : IUiTextService
|
|||||||
["Transformer Ansicht"] = "रूपांतरण दृश्य",
|
["Transformer Ansicht"] = "रूपांतरण दृश्य",
|
||||||
["Transformationscode"] = "रूपांतरण कोड",
|
["Transformationscode"] = "रूपांतरण कोड",
|
||||||
["Keine Beschreibung."] = "कोई विवरण नहीं.",
|
["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."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ public sealed class ExportAuditCsvServiceTests : IDisposable
|
|||||||
|
|
||||||
Assert.True(File.Exists(path));
|
Assert.True(File.Exists(path));
|
||||||
Assert.Equal(_tempDirectory, Path.GetDirectoryName(path));
|
Assert.Equal(_tempDirectory, Path.GetDirectoryName(path));
|
||||||
|
Assert.StartsWith("Sales_ProcessedMergeInput_TRCH_", Path.GetFileName(path), StringComparison.OrdinalIgnoreCase);
|
||||||
var records = await service.ReadLatestSiteAuditCsvRecordsAsync(settings);
|
var records = await service.ReadLatestSiteAuditCsvRecordsAsync(settings);
|
||||||
var roundtrip = Assert.Single(records);
|
var roundtrip = Assert.Single(records);
|
||||||
Assert.Equal("SAP", roundtrip.SourceSystem);
|
Assert.Equal("SAP", roundtrip.SourceSystem);
|
||||||
@@ -80,6 +81,62 @@ public sealed class ExportAuditCsvServiceTests : IDisposable
|
|||||||
Assert.Equal(new DateTime(2026, 6, 11), roundtrip.InvoiceDate);
|
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]
|
[Fact]
|
||||||
public async Task CentralSalesDataProvider_Uses_AuditCsv_When_Configured()
|
public async Task CentralSalesDataProvider_Uses_AuditCsv_When_Configured()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public sealed class SiteExportServiceTests : IDisposable
|
|||||||
|
|
||||||
Assert.NotNull(result.FilePath);
|
Assert.NotNull(result.FilePath);
|
||||||
Assert.True(File.Exists(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.True(File.Exists(auditCsv));
|
||||||
|
|
||||||
Assert.Equal(2, sharePoint.Uploads.Count);
|
Assert.Equal(2, sharePoint.Uploads.Count);
|
||||||
|
|||||||
Reference in New Issue
Block a user