Add SharePoint manual source handling and finance status
This commit is contained in:
@@ -11,4 +11,10 @@ public sealed class DataSourceFetchResult
|
||||
/// SiteExportService erzeugt dann keine neue Excel-Datei.
|
||||
/// </summary>
|
||||
public string? ReferenceFilePath { get; init; }
|
||||
|
||||
public string? LocalOutputDirectoryOverride { get; init; }
|
||||
|
||||
public string? SharePointUploadFolderOverride { get; init; }
|
||||
|
||||
public string? SharePointUploadLandOverride { get; init; }
|
||||
}
|
||||
|
||||
@@ -29,12 +29,15 @@ public sealed class ManualExcelDataSourceAdapter : IDataSourceAdapter
|
||||
|
||||
var manualImportPath = site.ManualImportFilePath.Trim();
|
||||
string filePath;
|
||||
string? localOutputDirectory = null;
|
||||
string? sharePointUploadFolder = null;
|
||||
string? tempManualImportPath = null;
|
||||
try
|
||||
{
|
||||
if (File.Exists(manualImportPath))
|
||||
{
|
||||
filePath = manualImportPath;
|
||||
localOutputDirectory = Path.GetDirectoryName(Path.GetFullPath(manualImportPath));
|
||||
}
|
||||
else if (LooksLikeSharePointReference(manualImportPath))
|
||||
{
|
||||
@@ -55,10 +58,22 @@ public sealed class ManualExcelDataSourceAdapter : IDataSourceAdapter
|
||||
await _appEventLogService.WriteAsync("Export", "Manuelle Excel von SharePoint laden",
|
||||
siteId: site.Id, land: site.Land, details: manualImportPath);
|
||||
|
||||
var sharePointFileReference = manualImportPath;
|
||||
if (LooksLikeSharePointFolderReference(manualImportPath))
|
||||
{
|
||||
var latestFile = await _sharePointService.ResolveLatestFileInFolderAsync(
|
||||
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret,
|
||||
spConfig.SiteUrl, manualImportPath, site.TSC);
|
||||
sharePointFileReference = latestFile.FileReference;
|
||||
await _appEventLogService.WriteAsync("Export", "Neueste SharePoint-Datei ausgewaehlt",
|
||||
siteId: site.Id, land: site.Land, details: sharePointFileReference);
|
||||
}
|
||||
|
||||
tempManualImportPath = await _sharePointService.DownloadToTempFileAsync(
|
||||
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret,
|
||||
spConfig.SiteUrl, manualImportPath);
|
||||
filePath = manualImportPath;
|
||||
spConfig.SiteUrl, sharePointFileReference);
|
||||
filePath = sharePointFileReference;
|
||||
sharePointUploadFolder = ResolveSharePointParentFolder(sharePointFileReference, spConfig.SiteUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -75,7 +90,9 @@ public sealed class ManualExcelDataSourceAdapter : IDataSourceAdapter
|
||||
return new DataSourceFetchResult
|
||||
{
|
||||
Records = records,
|
||||
ReferenceFilePath = filePath
|
||||
LocalOutputDirectoryOverride = localOutputDirectory,
|
||||
SharePointUploadFolderOverride = sharePointUploadFolder,
|
||||
SharePointUploadLandOverride = sharePointUploadFolder is null ? null : string.Empty
|
||||
};
|
||||
}
|
||||
finally
|
||||
@@ -90,4 +107,25 @@ public sealed class ManualExcelDataSourceAdapter : IDataSourceAdapter
|
||||
path.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.StartsWith("/Shared Documents/", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.StartsWith("Shared Documents/", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static bool LooksLikeSharePointFolderReference(string path)
|
||||
=> LooksLikeSharePointReference(path) &&
|
||||
string.IsNullOrWhiteSpace(Path.GetExtension(path.TrimEnd('/')));
|
||||
|
||||
private static string ResolveSharePointParentFolder(string fileReference, string siteUrl)
|
||||
{
|
||||
var remotePath = fileReference.Trim('/').Trim();
|
||||
if (Uri.TryCreate(fileReference, UriKind.Absolute, out var fileUri) &&
|
||||
Uri.TryCreate(siteUrl, UriKind.Absolute, out var siteUri))
|
||||
{
|
||||
var absolutePath = Uri.UnescapeDataString(fileUri.AbsolutePath);
|
||||
var sitePath = siteUri.AbsolutePath.TrimEnd('/');
|
||||
if (absolutePath.StartsWith(sitePath, StringComparison.OrdinalIgnoreCase))
|
||||
absolutePath = absolutePath[sitePath.Length..];
|
||||
remotePath = absolutePath.Trim('/').Trim();
|
||||
}
|
||||
|
||||
var lastSlash = remotePath.LastIndexOf('/');
|
||||
return lastSlash <= 0 ? string.Empty : remotePath[..lastSlash];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ public class DatabaseSeedService : IDatabaseSeedService
|
||||
EnsureSourceSystemDefinitions(db);
|
||||
EnsureCentralHanaServerRecords(db);
|
||||
EnsureSpainManualExcelSite(db);
|
||||
EnsureUkManualExcelFolder(db);
|
||||
EnsureSapODataDachSite(db);
|
||||
EnsureFinanceReferenceDefaults(db);
|
||||
EnsureBudgetExchangeRateDefaults(db);
|
||||
@@ -287,6 +288,36 @@ public class DatabaseSeedService : IDatabaseSeedService
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
private static void EnsureUkManualExcelFolder(AppDbContext db)
|
||||
{
|
||||
var existing = db.Sites
|
||||
.OrderBy(x => x.Id)
|
||||
.FirstOrDefault(x =>
|
||||
x.TSC == "TRUK" ||
|
||||
x.Land == "England" ||
|
||||
x.Land == "UK");
|
||||
|
||||
if (existing is null)
|
||||
return;
|
||||
|
||||
var changed = false;
|
||||
if (string.IsNullOrWhiteSpace(existing.SourceSystem))
|
||||
{
|
||||
existing.SourceSystem = "MANUAL_EXCEL";
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (string.Equals(existing.SourceSystem, "MANUAL_EXCEL", StringComparison.OrdinalIgnoreCase) &&
|
||||
string.IsNullOrWhiteSpace(existing.ManualImportFilePath))
|
||||
{
|
||||
existing.ManualImportFilePath = "https://trafagag.sharepoint.com/sites/WorldwideBIPlatform/Import/Finance/UK_B1";
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
private static void EnsureSapODataDachSite(AppDbContext db)
|
||||
{
|
||||
if (db.Sites.Count() <= 1)
|
||||
|
||||
@@ -60,16 +60,7 @@ public sealed class FinanceReconciliationService : IFinanceReconciliationService
|
||||
rows => BuildNetSalesActual(rows, budgetRatesToChf, intercompanyRules),
|
||||
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 financeReferences
|
||||
.Where(reference => activeSiteKeys.Contains(reference.Key) || groupedActuals.ContainsKey(reference.Key))
|
||||
.Select(reference => BuildReferenceRow(reference, groupedActuals))
|
||||
.OrderBy(row => row.Label, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
@@ -282,6 +273,8 @@ public sealed class FinanceReconciliationService : IFinanceReconciliationService
|
||||
var normalizedLand = (land ?? string.Empty).Trim().ToUpperInvariant();
|
||||
var normalizedTsc = (tsc ?? string.Empty).Trim().ToUpperInvariant();
|
||||
|
||||
if (normalizedLand is "AT" or "AUT" || normalizedLand.Contains("OESTER") || normalizedLand.Contains("OSTER") || normalizedLand.Contains("AUSTRIA")) return "AT";
|
||||
if (normalizedLand is "CH" or "CHE" || normalizedLand.Contains("SCHWE") || normalizedLand.Contains("SWITZER")) return "CH";
|
||||
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";
|
||||
@@ -289,7 +282,6 @@ public sealed class FinanceReconciliationService : IFinanceReconciliationService
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -4,5 +4,8 @@ public interface ISharePointUploadService
|
||||
{
|
||||
Task UploadAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string exportFolder, string land, string localFilePath);
|
||||
Task<string> DownloadToTempFileAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string fileReference);
|
||||
Task<SharePointFileReference> ResolveLatestFileInFolderAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string folderReference, string siteTsc);
|
||||
Task TestConnectionAsync(string tenantId, string clientId, string clientSecret, string siteUrl);
|
||||
}
|
||||
|
||||
public sealed record SharePointFileReference(string FileReference, DateTimeOffset? LastModifiedUtc);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using Azure.Core;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Graph;
|
||||
using Microsoft.Graph.Models;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
@@ -82,6 +85,64 @@ public class SharePointUploadService : ISharePointUploadService
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
public async Task<SharePointFileReference> ResolveLatestFileInFolderAsync(
|
||||
string tenantId,
|
||||
string clientId,
|
||||
string clientSecret,
|
||||
string siteUrl,
|
||||
string folderReference,
|
||||
string siteTsc)
|
||||
{
|
||||
var normalizedTenantId = Normalize(tenantId);
|
||||
var normalizedClientId = Normalize(clientId);
|
||||
var normalizedClientSecret = Normalize(clientSecret);
|
||||
var normalizedSiteUrl = Normalize(siteUrl);
|
||||
var normalizedReference = Normalize(folderReference);
|
||||
var normalizedTsc = Normalize(siteTsc).ToUpperInvariant();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(normalizedReference))
|
||||
throw new InvalidOperationException("SharePoint-Ordnerreferenz fehlt.");
|
||||
|
||||
var credential = new ClientSecretCredential(normalizedTenantId, normalizedClientId, normalizedClientSecret);
|
||||
var graphClient = new GraphServiceClient(credential, ["https://graph.microsoft.com/.default"]);
|
||||
|
||||
var siteUri = new Uri(normalizedSiteUrl);
|
||||
var sitePath = siteUri.AbsolutePath.TrimEnd('/');
|
||||
var site = await graphClient.Sites[$"{siteUri.Host}:{sitePath}"].GetAsync();
|
||||
|
||||
if (site?.Id is null)
|
||||
throw new InvalidOperationException("SharePoint Site konnte nicht gefunden werden.");
|
||||
|
||||
var drive = await graphClient.Sites[site.Id].Drive.GetAsync();
|
||||
if (drive?.Id is null)
|
||||
throw new InvalidOperationException("SharePoint Dokumentenbibliothek konnte nicht gefunden werden.");
|
||||
|
||||
var folderPath = ResolveRemotePath(normalizedReference, siteUri);
|
||||
var children = await graphClient.Drives[drive.Id].Root.ItemWithPath(folderPath).Children.GetAsync();
|
||||
var candidates = children?.Value?
|
||||
.Where(item => item.File is not null)
|
||||
.Where(item => IsSupportedManualImportFile(item.Name))
|
||||
.Where(item => MatchesTsc(item.Name, normalizedTsc))
|
||||
.Select(item => new
|
||||
{
|
||||
Item = item,
|
||||
FileDate = TryParseDatedSiteFileName(item.Name, normalizedTsc, out var fileDate) ? fileDate : (DateTime?)null
|
||||
})
|
||||
.OrderByDescending(x => x.FileDate ?? x.Item.LastModifiedDateTime?.UtcDateTime ?? DateTime.MinValue)
|
||||
.ThenByDescending(x => x.Item.LastModifiedDateTime?.UtcDateTime ?? DateTime.MinValue)
|
||||
.ToList() ?? [];
|
||||
|
||||
var selected = candidates.FirstOrDefault()
|
||||
?? throw new InvalidOperationException(
|
||||
string.IsNullOrWhiteSpace(normalizedTsc)
|
||||
? $"Im SharePoint-Ordner '{folderPath}' wurde keine Excel-/CSV-Datei gefunden."
|
||||
: $"Im SharePoint-Ordner '{folderPath}' wurde keine Excel-/CSV-Datei fuer '{normalizedTsc}' gefunden.");
|
||||
|
||||
return new SharePointFileReference(
|
||||
string.Join("/", folderPath.Trim('/'), selected.Item.Name).Trim('/'),
|
||||
selected.Item.LastModifiedDateTime);
|
||||
}
|
||||
|
||||
public async Task TestConnectionAsync(string tenantId, string clientId, string clientSecret, string siteUrl)
|
||||
{
|
||||
var normalizedTenantId = Normalize(tenantId);
|
||||
@@ -143,6 +204,41 @@ public class SharePointUploadService : ISharePointUploadService
|
||||
return fileReference.Trim('/').Trim();
|
||||
}
|
||||
|
||||
private static bool IsSupportedManualImportFile(string? fileName)
|
||||
{
|
||||
var extension = Path.GetExtension(fileName ?? string.Empty);
|
||||
return extension.Equals(".xlsx", StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals(".csv", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool MatchesTsc(string? fileName, string normalizedTsc)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(normalizedTsc))
|
||||
return true;
|
||||
|
||||
var nameWithoutExtension = Path.GetFileNameWithoutExtension(fileName ?? string.Empty);
|
||||
return nameWithoutExtension.EndsWith($"_{normalizedTsc}", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool TryParseDatedSiteFileName(string? fileName, string normalizedTsc, out DateTime fileDate)
|
||||
{
|
||||
fileDate = default;
|
||||
var nameWithoutExtension = Path.GetFileNameWithoutExtension(fileName ?? string.Empty);
|
||||
var pattern = string.IsNullOrWhiteSpace(normalizedTsc)
|
||||
? @"^(?<date>\d{6})_[A-Z0-9]+$"
|
||||
: $"^(?<date>\\d{{6}})_{Regex.Escape(normalizedTsc)}$";
|
||||
var match = Regex.Match(nameWithoutExtension, pattern, RegexOptions.IgnoreCase);
|
||||
if (!match.Success)
|
||||
return false;
|
||||
|
||||
return DateTime.TryParseExact(
|
||||
match.Groups["date"].Value,
|
||||
"ddMMyy",
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.None,
|
||||
out fileDate);
|
||||
}
|
||||
|
||||
private static string BuildInputPreview(string tenantId, string clientId, string clientSecret, string siteUrl)
|
||||
{
|
||||
var maskedSecret = string.IsNullOrEmpty(clientSecret)
|
||||
|
||||
@@ -56,8 +56,6 @@ public class SiteExportService : ISiteExportService
|
||||
details: $"Quelle={sourceSystem} | TSC={site.TSC}");
|
||||
|
||||
var (settings, spConfig, sourceDefinition, rules) = await LoadExportConfigAsync(site, sourceSystem);
|
||||
var outputDir = ResolveSiteOutputDirectory(settings, site);
|
||||
|
||||
var adapter = _dataSourceResolver.Resolve(sourceDefinition.ConnectionKind);
|
||||
var fetchResult = await adapter.FetchAsync(new DataSourceFetchContext
|
||||
{
|
||||
@@ -69,6 +67,7 @@ public class SiteExportService : ISiteExportService
|
||||
});
|
||||
|
||||
var records = fetchResult.Records;
|
||||
var outputDir = fetchResult.LocalOutputDirectoryOverride ?? ResolveSiteOutputDirectory(settings, site);
|
||||
|
||||
updateStatus?.Invoke("Transformationen anwenden...");
|
||||
await _appEventLogService.WriteAsync("Export", "Transformationen anwenden",
|
||||
@@ -94,7 +93,7 @@ public class SiteExportService : ISiteExportService
|
||||
details: $"Records={records.Count}");
|
||||
await _centralSalesRecordService.ReplaceForSiteAsync(site, records, updateStatus);
|
||||
|
||||
await UploadToSharePointIfConfiguredAsync(site, spConfig, filePath, updateStatus);
|
||||
await UploadToSharePointIfConfiguredAsync(site, spConfig, filePath, updateStatus, fetchResult);
|
||||
|
||||
sw.Stop();
|
||||
log.Status = "OK";
|
||||
@@ -156,7 +155,11 @@ public class SiteExportService : ISiteExportService
|
||||
}
|
||||
|
||||
private async Task UploadToSharePointIfConfiguredAsync(
|
||||
Site site, SharePointConfig? spConfig, string filePath, Action<string>? updateStatus)
|
||||
Site site,
|
||||
SharePointConfig? spConfig,
|
||||
string filePath,
|
||||
Action<string>? updateStatus,
|
||||
DataSourceFetchResult fetchResult)
|
||||
{
|
||||
if (spConfig is null ||
|
||||
string.IsNullOrWhiteSpace(spConfig.TenantId) ||
|
||||
@@ -165,12 +168,16 @@ public class SiteExportService : ISiteExportService
|
||||
return;
|
||||
|
||||
updateStatus?.Invoke("SharePoint Upload...");
|
||||
var uploadFolder = string.IsNullOrWhiteSpace(fetchResult.SharePointUploadFolderOverride)
|
||||
? spConfig.ExportFolder
|
||||
: fetchResult.SharePointUploadFolderOverride;
|
||||
var uploadLand = fetchResult.SharePointUploadLandOverride ?? site.Land;
|
||||
await _appEventLogService.WriteAsync("Export", "SharePoint Upload gestartet",
|
||||
siteId: site.Id, land: site.Land,
|
||||
details: $"{spConfig.SiteUrl} | {spConfig.ExportFolder}");
|
||||
details: $"{spConfig.SiteUrl} | {uploadFolder}");
|
||||
await _sharePointService.UploadAsync(
|
||||
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret,
|
||||
spConfig.SiteUrl, spConfig.ExportFolder, site.Land, filePath);
|
||||
spConfig.SiteUrl, uploadFolder, uploadLand, filePath);
|
||||
}
|
||||
|
||||
private static string NormalizeSourceSystem(string? sourceSystem)
|
||||
|
||||
@@ -409,13 +409,14 @@ public sealed class StandortePageService : IStandortePageService
|
||||
var trimmedPath = manualImportFilePath.Trim();
|
||||
if (string.IsNullOrWhiteSpace(trimmedPath))
|
||||
throw new InvalidOperationException("Bitte zuerst einen Dateipfad eintragen.");
|
||||
if (!IsSupportedManualImportFile(trimmedPath))
|
||||
var isSharePointReference = LooksLikeSharePointReference(trimmedPath);
|
||||
if (!isSharePointReference && !IsSupportedManualImportFile(trimmedPath))
|
||||
throw new InvalidOperationException("Bitte eine Excel- oder CSV-Datei mit Endung .xlsx oder .csv angeben.");
|
||||
|
||||
if (File.Exists(trimmedPath))
|
||||
return File.GetLastWriteTimeUtc(trimmedPath);
|
||||
|
||||
if (!LooksLikeSharePointReference(trimmedPath))
|
||||
if (!isSharePointReference)
|
||||
throw new InvalidOperationException($"Datei nicht gefunden oder nicht erreichbar: {trimmedPath}");
|
||||
|
||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||
@@ -429,8 +430,16 @@ public sealed class StandortePageService : IStandortePageService
|
||||
throw new InvalidOperationException("Fuer SharePoint-Pruefung fehlt eine vollstaendige SharePoint-Konfiguration in Settings.");
|
||||
}
|
||||
|
||||
var sharePointFileReference = trimmedPath;
|
||||
if (!IsSupportedManualImportFile(trimmedPath))
|
||||
{
|
||||
var latestFile = await _sharePointService.ResolveLatestFileInFolderAsync(
|
||||
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret, spConfig.SiteUrl, trimmedPath, string.Empty);
|
||||
sharePointFileReference = latestFile.FileReference;
|
||||
}
|
||||
|
||||
var tempPath = await _sharePointService.DownloadToTempFileAsync(
|
||||
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret, spConfig.SiteUrl, trimmedPath);
|
||||
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret, spConfig.SiteUrl, sharePointFileReference);
|
||||
try
|
||||
{
|
||||
return File.GetLastWriteTimeUtc(tempPath);
|
||||
@@ -448,7 +457,7 @@ public sealed class StandortePageService : IStandortePageService
|
||||
var deleteAfterRead = !string.Equals(filePath, manualImportFilePath?.Trim(), StringComparison.OrdinalIgnoreCase);
|
||||
try
|
||||
{
|
||||
return string.Equals(Path.GetExtension(manualImportFilePath?.Trim()), ".csv", StringComparison.OrdinalIgnoreCase)
|
||||
return string.Equals(Path.GetExtension(filePath), ".csv", StringComparison.OrdinalIgnoreCase)
|
||||
? LoadCsvHeaders(filePath)
|
||||
: LoadExcelHeaders(filePath);
|
||||
}
|
||||
@@ -482,8 +491,16 @@ public sealed class StandortePageService : IStandortePageService
|
||||
throw new InvalidOperationException("Fuer SharePoint-Pruefung fehlt eine vollstaendige SharePoint-Konfiguration in Settings.");
|
||||
}
|
||||
|
||||
var sharePointFileReference = trimmedPath;
|
||||
if (!IsSupportedManualImportFile(trimmedPath))
|
||||
{
|
||||
var latestFile = await _sharePointService.ResolveLatestFileInFolderAsync(
|
||||
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret, spConfig.SiteUrl, trimmedPath, string.Empty);
|
||||
sharePointFileReference = latestFile.FileReference;
|
||||
}
|
||||
|
||||
return await _sharePointService.DownloadToTempFileAsync(
|
||||
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret, spConfig.SiteUrl, trimmedPath);
|
||||
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret, spConfig.SiteUrl, sharePointFileReference);
|
||||
}
|
||||
|
||||
private static void ApplyServer(HanaServer target, HanaServer source)
|
||||
|
||||
Reference in New Issue
Block a user