Support Spain sales delta folder sync
This commit is contained in:
@@ -31,6 +31,7 @@ public sealed class ManualExcelDataSourceAdapter : IDataSourceAdapter
|
||||
string filePath;
|
||||
string? localOutputDirectory = null;
|
||||
string? sharePointUploadFolder = null;
|
||||
var localManualImportPaths = new List<string>();
|
||||
var tempManualImportPaths = new List<string>();
|
||||
try
|
||||
{
|
||||
@@ -39,6 +40,12 @@ public sealed class ManualExcelDataSourceAdapter : IDataSourceAdapter
|
||||
filePath = manualImportPath;
|
||||
localOutputDirectory = Path.GetDirectoryName(Path.GetFullPath(manualImportPath));
|
||||
}
|
||||
else if (Directory.Exists(manualImportPath))
|
||||
{
|
||||
localManualImportPaths.AddRange(ResolveLocalManualImportFilesInFolder(manualImportPath, site));
|
||||
filePath = manualImportPath;
|
||||
localOutputDirectory = Path.GetFullPath(manualImportPath);
|
||||
}
|
||||
else if (LooksLikeSharePointReference(manualImportPath))
|
||||
{
|
||||
var spConfig = context.SharePointConfig
|
||||
@@ -95,9 +102,15 @@ public sealed class ManualExcelDataSourceAdapter : IDataSourceAdapter
|
||||
siteId: site.Id, land: site.Land, details: filePath);
|
||||
|
||||
var records = new List<SalesRecord>();
|
||||
var readPaths = tempManualImportPaths.Count > 0 ? tempManualImportPaths : [filePath];
|
||||
var readPaths = tempManualImportPaths.Count > 0
|
||||
? tempManualImportPaths
|
||||
: localManualImportPaths.Count > 0
|
||||
? localManualImportPaths
|
||||
: [filePath];
|
||||
foreach (var readPath in readPaths)
|
||||
records.AddRange(await _manualExcelImportService.ReadSalesRecordsAsync(readPath, site));
|
||||
if (IsSpainSite(site))
|
||||
records = DeduplicateSpainSalesRecords(records);
|
||||
return new DataSourceFetchResult
|
||||
{
|
||||
Records = records,
|
||||
@@ -126,6 +139,85 @@ public sealed class ManualExcelDataSourceAdapter : IDataSourceAdapter
|
||||
=> LooksLikeSharePointReference(path) &&
|
||||
string.IsNullOrWhiteSpace(Path.GetExtension(path.TrimEnd('/')));
|
||||
|
||||
private static List<string> ResolveLocalManualImportFilesInFolder(string folderPath, Site site)
|
||||
{
|
||||
var files = Directory.EnumerateFiles(folderPath)
|
||||
.Where(IsSupportedManualImportFile)
|
||||
.Where(path => !IsSpainSite(site) || IsSpainSalesFile(path))
|
||||
.OrderBy(GetManualImportFileSortKey, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(path => path, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
if (files.Count == 0)
|
||||
{
|
||||
var expected = IsSpainSite(site) ? "Spain_Sales*.csv" : "*.xlsx/*.csv";
|
||||
throw new InvalidOperationException($"Im Ordner '{folderPath}' wurde keine passende Importdatei gefunden ({expected}).");
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
private static bool IsSupportedManualImportFile(string path)
|
||||
{
|
||||
var extension = Path.GetExtension(path);
|
||||
return extension.Equals(".xlsx", StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals(".csv", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsSpainSite(Site site)
|
||||
=> string.Equals(site.TSC, "TRES", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(site.TSC, "TRSE", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(site.Land, "Spanien", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(site.Land, "Spain", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static bool IsSpainSalesFile(string path)
|
||||
=> Path.GetFileName(path).StartsWith("Spain_Sales", StringComparison.OrdinalIgnoreCase) &&
|
||||
Path.GetExtension(path).Equals(".csv", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static string GetManualImportFileSortKey(string path)
|
||||
{
|
||||
var name = Path.GetFileNameWithoutExtension(path);
|
||||
var rangeIndex = name.IndexOf("_range_", StringComparison.OrdinalIgnoreCase);
|
||||
if (rangeIndex >= 0)
|
||||
return "1_" + name[(rangeIndex + "_range_".Length)..];
|
||||
|
||||
return "0_" + name;
|
||||
}
|
||||
|
||||
private static List<SalesRecord> DeduplicateSpainSalesRecords(IEnumerable<SalesRecord> records)
|
||||
{
|
||||
var ordered = records.ToList();
|
||||
var keyed = new Dictionary<string, SalesRecord>(StringComparer.OrdinalIgnoreCase);
|
||||
var unkeyed = new List<SalesRecord>();
|
||||
|
||||
foreach (var record in ordered)
|
||||
{
|
||||
var key = BuildSpainSalesRecordKey(record);
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
unkeyed.Add(record);
|
||||
else
|
||||
keyed[key] = record;
|
||||
}
|
||||
|
||||
return keyed.Values.Concat(unkeyed).ToList();
|
||||
}
|
||||
|
||||
private static string BuildSpainSalesRecordKey(SalesRecord record)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(record.SourceLineId))
|
||||
return $"source:{record.SourceLineId.Trim()}";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(record.InvoiceNumber))
|
||||
return string.Join("|",
|
||||
"invoice",
|
||||
record.Tsc?.Trim() ?? string.Empty,
|
||||
record.InvoiceNumber.Trim(),
|
||||
record.PositionOnInvoice.ToString(System.Globalization.CultureInfo.InvariantCulture),
|
||||
record.Material?.Trim() ?? string.Empty);
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static string ResolveSharePointParentFolder(string fileReference, string siteUrl)
|
||||
{
|
||||
var remotePath = fileReference.Trim('/').Trim();
|
||||
|
||||
@@ -18,6 +18,7 @@ public class ManualExcelImportService : IManualExcelImportService
|
||||
{
|
||||
["extractiondate"] = nameof(SalesRecord.ExtractionDate),
|
||||
["tsc"] = nameof(SalesRecord.Tsc),
|
||||
["sourcelineid"] = nameof(SalesRecord.SourceLineId),
|
||||
["documententry"] = nameof(SalesRecord.DocumentEntry),
|
||||
["invoicenumber"] = nameof(SalesRecord.InvoiceNumber),
|
||||
["positiononinvoice"] = nameof(SalesRecord.PositionOnInvoice),
|
||||
@@ -163,6 +164,7 @@ public class ManualExcelImportService : IManualExcelImportService
|
||||
{
|
||||
ExtractionDate = ReadDate(headerIndexes, fields, nameof(SalesRecord.ExtractionDate)) ?? DateTime.UtcNow,
|
||||
Tsc = ReadString(headerIndexes, fields, nameof(SalesRecord.Tsc), site.TSC),
|
||||
SourceLineId = ReadString(headerIndexes, fields, nameof(SalesRecord.SourceLineId)),
|
||||
DocumentEntry = (int)Math.Round(ReadDecimal(headerIndexes, fields, nameof(SalesRecord.DocumentEntry))),
|
||||
InvoiceNumber = ReadString(headerIndexes, fields, nameof(SalesRecord.InvoiceNumber)),
|
||||
PositionOnInvoice = (int)Math.Round(ReadDecimal(headerIndexes, fields, nameof(SalesRecord.PositionOnInvoice))),
|
||||
@@ -281,6 +283,7 @@ public class ManualExcelImportService : IManualExcelImportService
|
||||
{
|
||||
ExtractionDate = ReadDate(headerIndexes, row, nameof(SalesRecord.ExtractionDate)) ?? DateTime.UtcNow,
|
||||
Tsc = ReadString(headerIndexes, row, nameof(SalesRecord.Tsc), site.TSC),
|
||||
SourceLineId = ReadString(headerIndexes, row, nameof(SalesRecord.SourceLineId)),
|
||||
DocumentEntry = (int)Math.Round(ReadDecimal(headerIndexes, row, nameof(SalesRecord.DocumentEntry))),
|
||||
InvoiceNumber = ReadString(headerIndexes, row, nameof(SalesRecord.InvoiceNumber)),
|
||||
PositionOnInvoice = (int)Math.Round(ReadDecimal(headerIndexes, row, nameof(SalesRecord.PositionOnInvoice))),
|
||||
|
||||
@@ -113,6 +113,7 @@ public class SharePointUploadService : ISharePointUploadService
|
||||
var normalizedSiteUrl = Normalize(siteUrl);
|
||||
var normalizedReference = Normalize(folderReference);
|
||||
var normalizedTsc = Normalize(siteTsc).ToUpperInvariant();
|
||||
var isSpainImport = IsSpainManualImport(normalizedTsc, normalizedReference);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(normalizedReference))
|
||||
throw new InvalidOperationException("SharePoint-Ordnerreferenz fehlt.");
|
||||
@@ -136,16 +137,41 @@ public class SharePointUploadService : ISharePointUploadService
|
||||
var allCandidates = children?.Value?
|
||||
.Where(item => item.File is not null)
|
||||
.Where(item => IsSupportedManualImportFile(item.Name))
|
||||
.Where(item => MatchesTsc(item.Name, normalizedTsc))
|
||||
.Select(item => new
|
||||
.Where(item => isSpainImport ? IsSpainSalesFile(item.Name) : MatchesTsc(item.Name, normalizedTsc))
|
||||
.Select(item =>
|
||||
{
|
||||
Item = item,
|
||||
FileDate = TryParseDatedSiteFileName(item.Name, normalizedTsc, out var fileDate) ? fileDate : (DateTime?)null,
|
||||
AnnualYear = TryParseAnnualSiteFileName(item.Name, normalizedTsc, out var annualYear) ? annualYear : (int?)null,
|
||||
SnapshotDate = TryParseSnapshotDate(item.Name, out var snapshotDate) ? snapshotDate : (DateTime?)null
|
||||
var hasSpainRange = TryParseSpainSalesRangeFileName(item.Name, out var rangeStart, out var rangeEnd);
|
||||
return new
|
||||
{
|
||||
Item = item,
|
||||
FileDate = TryParseDatedSiteFileName(item.Name, normalizedTsc, out var fileDate) ? fileDate : (DateTime?)null,
|
||||
SpainRangeStart = hasSpainRange ? rangeStart : (DateTime?)null,
|
||||
SpainRangeEnd = hasSpainRange ? rangeEnd : (DateTime?)null,
|
||||
AnnualYear = TryParseAnnualSiteFileName(item.Name, normalizedTsc, out var annualYear) ? annualYear : (int?)null,
|
||||
SnapshotDate = TryParseSnapshotDate(item.Name, out var snapshotDate) ? snapshotDate : (DateTime?)null
|
||||
};
|
||||
})
|
||||
.ToList() ?? [];
|
||||
|
||||
if (isSpainImport)
|
||||
{
|
||||
var spainCandidates = allCandidates
|
||||
.OrderBy(x => x.SpainRangeStart is null ? 0 : 1)
|
||||
.ThenBy(x => x.SpainRangeStart ?? DateTime.MinValue)
|
||||
.ThenBy(x => x.SpainRangeEnd ?? DateTime.MinValue)
|
||||
.ThenBy(x => x.Item.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
if (spainCandidates.Count == 0)
|
||||
throw new InvalidOperationException($"Im SharePoint-Ordner '{folderPath}' wurde keine Spain_Sales*.csv gefunden.");
|
||||
|
||||
return spainCandidates
|
||||
.Select(x => new SharePointFileReference(
|
||||
string.Join("/", folderPath.Trim('/'), x.Item.Name).Trim('/'),
|
||||
x.Item.LastModifiedDateTime))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
if (preferredYear is not null)
|
||||
{
|
||||
var annual = allCandidates
|
||||
@@ -315,6 +341,39 @@ public class SharePointUploadService : ISharePointUploadService
|
||||
Regex.IsMatch(nameWithoutExtension, $@"(^|[^A-Z0-9]){Regex.Escape(normalizedTsc)}([^A-Z0-9]|$)", RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsSpainManualImport(string normalizedTsc, string folderReference)
|
||||
=> string.Equals(normalizedTsc, "TRES", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(normalizedTsc, "TRSE", StringComparison.OrdinalIgnoreCase) ||
|
||||
folderReference.Contains("Spanien", StringComparison.OrdinalIgnoreCase) ||
|
||||
folderReference.Contains("Spain", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static bool IsSpainSalesFile(string? fileName)
|
||||
=> Path.GetFileName(fileName ?? string.Empty).StartsWith("Spain_Sales", StringComparison.OrdinalIgnoreCase) &&
|
||||
Path.GetExtension(fileName ?? string.Empty).Equals(".csv", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static bool TryParseSpainSalesRangeFileName(string? fileName, out DateTime rangeStart, out DateTime rangeEnd)
|
||||
{
|
||||
rangeStart = default;
|
||||
rangeEnd = default;
|
||||
var nameWithoutExtension = Path.GetFileNameWithoutExtension(fileName ?? string.Empty);
|
||||
var match = Regex.Match(nameWithoutExtension, @"^Spain_Sales_range_(?<from>\d{8})_to_(?<to>\d{8})$", RegexOptions.IgnoreCase);
|
||||
if (!match.Success)
|
||||
return false;
|
||||
|
||||
return DateTime.TryParseExact(
|
||||
match.Groups["from"].Value,
|
||||
"yyyyMMdd",
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.None,
|
||||
out rangeStart) &&
|
||||
DateTime.TryParseExact(
|
||||
match.Groups["to"].Value,
|
||||
"yyyyMMdd",
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.None,
|
||||
out rangeEnd);
|
||||
}
|
||||
|
||||
private static bool TryParseDatedSiteFileName(string? fileName, string normalizedTsc, out DateTime fileDate)
|
||||
{
|
||||
fileDate = default;
|
||||
|
||||
Reference in New Issue
Block a user