Convert TrafagSalesExporter from console app to Blazor Server app with MudBlazor UI

- Replaced console app with .NET 8 Blazor Server architecture
- Added EF Core SQLite database (trafag_exporter.db) with auto-seed data
- Models: HanaServer, Site, SharePointConfig, ExportSettings, ExportLog, SalesRecord
- Services: HanaQueryService (with configurable dateFilter), ExcelExportService,
  SharePointUploadService, ExportOrchestrationService (with live status events),
  TimerBackgroundService (scheduled daily export)
- MudBlazor UI pages: Dashboard (export status + manual trigger), Standorte
  (HANA server + site CRUD), Settings (SharePoint + timer config), Logs (filtered view)
- SAP HANA queries unchanged (INV + CRN with exact SAP B1 table joins)
- SharePoint upload via Microsoft Graph with app registration auth

https://claude.ai/code/session_012heAXNMbbyxqYf2S2HrKLj
This commit is contained in:
Claude
2026-04-09 14:00:44 +00:00
parent 2f56082adc
commit 8524631508
23 changed files with 1327 additions and 154 deletions
@@ -5,40 +5,41 @@ namespace TrafagSalesExporter.Services;
public class SharePointUploadService
{
private readonly GraphServiceClient _graphClient;
private readonly string _siteUrl;
private readonly string _exportFolder;
public SharePointUploadService(string tenantId, string clientId, string clientSecret, string siteUrl, string exportFolder)
public async Task UploadAsync(string tenantId, string clientId, string clientSecret,
string siteUrl, string exportFolder, string land, string localFilePath)
{
var credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
_graphClient = new GraphServiceClient(credential, ["https://graph.microsoft.com/.default"]);
_siteUrl = siteUrl;
_exportFolder = exportFolder;
}
var graphClient = new GraphServiceClient(credential, ["https://graph.microsoft.com/.default"]);
public async Task UploadAsync(string land, string localFilePath)
{
var uri = new Uri(_siteUrl);
var uri = new Uri(siteUrl);
var sitePath = uri.AbsolutePath;
var site = await _graphClient.Sites[$"{uri.Host}:{sitePath}"].GetAsync();
var site = await graphClient.Sites[$"{uri.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();
var drive = await graphClient.Sites[site.Id].Drive.GetAsync();
if (drive?.Id is null)
{
throw new InvalidOperationException("SharePoint Dokumentenbibliothek konnte nicht gefunden werden.");
}
var fileName = Path.GetFileName(localFilePath);
var folderPath = $"{_exportFolder.Trim('/').Trim()}";
var folderPath = exportFolder.Trim('/').Trim();
var remotePath = $"{folderPath}/{land}/{fileName}";
await using var stream = File.OpenRead(localFilePath);
await _graphClient.Drives[drive.Id].Root.ItemWithPath(remotePath).Content.PutAsync(stream);
await graphClient.Drives[drive.Id].Root.ItemWithPath(remotePath).Content.PutAsync(stream);
}
public async Task TestConnectionAsync(string tenantId, string clientId, string clientSecret, string siteUrl)
{
var credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(credential, ["https://graph.microsoft.com/.default"]);
var uri = new Uri(siteUrl);
var sitePath = uri.AbsolutePath;
var site = await graphClient.Sites[$"{uri.Host}:{sitePath}"].GetAsync();
if (site?.Id is null)
throw new InvalidOperationException("SharePoint Site konnte nicht gefunden werden.");
}
}