Add bank transaction import
This commit is contained in:
parent
e2802dd797
commit
0828dd7691
8 changed files with 188 additions and 43 deletions
|
|
@ -1,5 +0,0 @@
|
||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABar_002Ecs_002Fl_003AC_0021_003FUsers_003Fschaa_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F3d754f61d7ca16fefcc624d522ddfb3235d54b85ab5638c1e181cde5af4357d_003FBar_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003AC_0021_003FUsers_003Fschaa_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fbd1d5c50194fea68ff3559c160230b0ab50f5acf4ce3061bffd6d62958e2182_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHttpHeaders_002Ecs_002Fl_003AC_0021_003FUsers_003Fschaa_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F66e1248da26799c021f14a7e458d63250ea9fa0bcc979f1e5a1a43ca6f3cc7_003FHttpHeaders_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003AC_0021_003FUsers_003Fschaa_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F6354a7b35d7821629924d3676acd7e67a6f7f94343e0e66ec439aa2bd6ed5_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
|
||||||
|
|
@ -25,18 +25,6 @@ namespace Schaad.Accounting.Datasets
|
||||||
Status = status;
|
Status = status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetCssAlertClass()
|
|
||||||
{
|
|
||||||
switch (Status)
|
|
||||||
{
|
|
||||||
case MessageStatus.Success: return "alert-success";
|
|
||||||
case MessageStatus.Info: return "alert-info";
|
|
||||||
case MessageStatus.Warning: return "alert-warning";
|
|
||||||
case MessageStatus.Error: return "alert-danger";
|
|
||||||
default: return "alert-success";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum MessageStatus
|
public enum MessageStatus
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
<FluentStack Class="main" Orientation="Orientation.Horizontal" Width="100%">
|
<FluentStack Class="main" Orientation="Orientation.Horizontal" Width="100%">
|
||||||
<NavMenu />
|
<NavMenu />
|
||||||
<FluentBodyContent Class="body-content">
|
<FluentBodyContent Class="body-content">
|
||||||
|
<FluentMessageBarProvider Section="MESSAGES_TOP" @rendermode="InteractiveServer" />
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@Body
|
@Body
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -21,6 +22,7 @@
|
||||||
</FluentLayout>
|
</FluentLayout>
|
||||||
|
|
||||||
<FluentDialogProvider @rendermode="InteractiveServer"/>
|
<FluentDialogProvider @rendermode="InteractiveServer"/>
|
||||||
|
<FluentToastProvider MaxToastCount="10" @rendermode="InteractiveServer" />
|
||||||
|
|
||||||
<div id="blazor-error-ui" data-nosnippet>
|
<div id="blazor-error-ui" data-nosnippet>
|
||||||
An unhandled error has occurred.
|
An unhandled error has occurred.
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,3 @@ AccountingNext
|
||||||
@bind-SelectedOption="@selectedMandator"
|
@bind-SelectedOption="@selectedMandator"
|
||||||
ValueChanged="MandatorChanged"/>
|
ValueChanged="MandatorChanged"/>
|
||||||
</FluentStack>
|
</FluentStack>
|
||||||
|
|
||||||
@code {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -8,6 +8,9 @@ public partial class MyHeader : ComponentBase
|
||||||
[Inject]
|
[Inject]
|
||||||
private ISettingsService settingsService { get; set; } = null!;
|
private ISettingsService settingsService { get; set; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
NavigationManager navigationManager { get; set; } = null!;
|
||||||
|
|
||||||
private List<int> years = new ();
|
private List<int> years = new ();
|
||||||
private int selectedYear;
|
private int selectedYear;
|
||||||
|
|
||||||
|
|
@ -32,12 +35,20 @@ public partial class MyHeader : ComponentBase
|
||||||
public void YearChanged(string syear)
|
public void YearChanged(string syear)
|
||||||
{
|
{
|
||||||
var year = int.Parse(syear);
|
var year = int.Parse(syear);
|
||||||
settingsService.SetYear(year);
|
if (selectedYear != year)
|
||||||
mandators = settingsService.GetMandators(year).ToList();
|
{
|
||||||
|
settingsService.SetYear(year);
|
||||||
|
mandators = settingsService.GetMandators(year).ToList();
|
||||||
|
navigationManager.NavigateTo(navigationManager.Uri, forceLoad: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MandatorChanged(string mandator)
|
public void MandatorChanged(string mandator)
|
||||||
{
|
{
|
||||||
settingsService.SetMandator(mandator);
|
if (selectedMandator != mandator)
|
||||||
|
{
|
||||||
|
settingsService.SetMandator(mandator);
|
||||||
|
navigationManager.NavigateTo(navigationManager.Uri, forceLoad: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,16 +4,14 @@
|
||||||
<FluentPaginator State="@pagination" />
|
<FluentPaginator State="@pagination" />
|
||||||
|
|
||||||
<div id="datagrid-container">
|
<div id="datagrid-container">
|
||||||
|
<FluentDataGrid Items="@TransactionList" Pagination="@pagination" RowSize="DataGridRowSize.Small" AutoItemsPerPage="true" Style="overflow-y:hidden;">
|
||||||
<FluentDataGrid Items="@TransactionList" Pagination="@pagination" RowSize="@rowSize" AutoItemsPerPage="true" Style="overflow-y:hidden;">
|
<PropertyColumn Title="Buchungsdatum" Width="170px" Property="@(p => p.BookingDate)" Format="dd.MM.yyyy" Sortable="true" IsDefaultSortColumn="true" InitialSortDirection="SortDirection.Descending" />
|
||||||
<PropertyColumn Title="Buchungsdatum" Width="100" Property="@(p => p.BookingDate)" Format="dd.MM.yyyy" Sortable="true" IsDefaultSortColumn="true" InitialSortDirection="SortDirection.Descending" />
|
<PropertyColumn Title="Valutadatum" Width="170px" Property="@(p => p.ValueDate)" Format="dd.MM.yyyy" Sortable="true" />
|
||||||
<PropertyColumn Title="Valutadatum" Width="100" Property="@(p => p.ValueDate)" Format="dd.MM.yyyy" Sortable="true" />
|
|
||||||
<PropertyColumn Title="Text" Property="@(p => p.Text)" />
|
<PropertyColumn Title="Text" Property="@(p => p.Text)" />
|
||||||
<PropertyColumn Title="Betrag" Width="100" Property="@(p => p.Value)" Format="C2" Align="Align.End"/>
|
<PropertyColumn Title="Betrag" Width="150px" Property="@(p => p.Value)" Format="C2" Align="Align.End"/>
|
||||||
</FluentDataGrid>
|
</FluentDataGrid>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
DataGridRowSize rowSize = DataGridRowSize.Small;
|
|
||||||
PaginationState pagination = new PaginationState() { ItemsPerPage = 10 };
|
PaginationState pagination = new PaginationState() { ItemsPerPage = 10 };
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,44 @@
|
||||||
@page "/"
|
@page "/"
|
||||||
<h3>Home</h3>
|
@rendermode InteractiveServer
|
||||||
|
|
||||||
@foreach (var bankTransaction in bankTransactions)
|
<PageTitle>Home</PageTitle>
|
||||||
{
|
|
||||||
<p>@bankTransaction.BankAccountNumber</p>
|
<FluentInputFile @ref="@myFileByBuffer"
|
||||||
}
|
AnchorId="MyUploadBuffer"
|
||||||
|
DragDropZoneVisible="false"
|
||||||
|
Mode="InputFileMode.Buffer"
|
||||||
|
Multiple="true"
|
||||||
|
MaximumFileSize="@(10 * 1024 * 1024)"
|
||||||
|
Accept=".zip, .xml"
|
||||||
|
OnProgressChange="@OnProgressChangeAsync"
|
||||||
|
OnCompleted="@OnCompletedAsync" />
|
||||||
|
|
||||||
|
<FluentProgress Visible="@(progressPercent > 0)" Min="0" Max="100" Value="@progressPercent" />
|
||||||
|
<FluentLabel Alignment="HorizontalAlignment.Center">
|
||||||
|
@progressTitle
|
||||||
|
</FluentLabel>
|
||||||
|
|
||||||
|
<FluentButton Appearance="Appearance.Lightweight" IconStart="@(new Icons.Regular.Size16.ArrowUpload())" Id="MyUploadBuffer">
|
||||||
|
Upload files
|
||||||
|
</FluentButton>
|
||||||
|
|
||||||
|
<FluentButton Appearance="Appearance.Lightweight" IconStart="@(new Icons.Regular.Size16.Dismiss())" @onclick="@((e) => IsCanceled = true)">
|
||||||
|
Cancel
|
||||||
|
</FluentButton>
|
||||||
|
|
||||||
|
<div id="datagrid-container">
|
||||||
|
<FluentDataGrid Items="@bankTransactionsQueryable" RowSize="DataGridRowSize.Small" AutoItemsPerPage="true">
|
||||||
|
<ChildContent>
|
||||||
|
<PropertyColumn Title="Buchungsdatum" Width="170px" Property="@(p => p.BookingDate)" Format="dd.MM.yyyy" Sortable="true" IsDefaultSortColumn="true" InitialSortDirection="SortDirection.Descending"/>
|
||||||
|
<PropertyColumn Title="Valutadatum" Width="170px" Property="@(p => p.ValueDate)" Format="dd.MM.yyyy" Sortable="true"/>
|
||||||
|
<PropertyColumn Title="Text" Property="@(p => p.Text)"/>
|
||||||
|
<PropertyColumn Title="Betrag" Width="150px" Property="@(p => p.Value)" Format="C2" Align="Align.End"/>
|
||||||
|
</ChildContent>
|
||||||
|
<EmptyContent>
|
||||||
|
<FluentStack Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center" HorizontalAlignment="HorizontalAlignment.Center" HorizontalGap="10">
|
||||||
|
<FluentIcon Value="@(new Icons.Filled.Size24.Crown())" Color="@Color.Accent"/>
|
||||||
|
<div>Keine Daten vorhanden!</div>
|
||||||
|
</FluentStack>
|
||||||
|
</EmptyContent>
|
||||||
|
</FluentDataGrid>
|
||||||
|
</div>
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
using Microsoft.AspNetCore.Components;
|
using System.IO.Compression;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.FluentUI.AspNetCore.Components;
|
||||||
|
using Schaad.Accounting.Datasets;
|
||||||
using Schaad.Accounting.Interfaces;
|
using Schaad.Accounting.Interfaces;
|
||||||
using Schaad.Accounting.Models;
|
using Schaad.Accounting.Models;
|
||||||
|
|
||||||
|
|
@ -9,11 +12,126 @@ public partial class Home : ComponentBase
|
||||||
[Inject]
|
[Inject]
|
||||||
private IViewService viewService { get; set; } = null!;
|
private IViewService viewService { get; set; } = null!;
|
||||||
|
|
||||||
private List<BankTransaction> bankTransactions = null!;
|
[Inject]
|
||||||
|
private IToastService toastService { get; set; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private IMessageService messageService { get; set; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private ISettingsService settingsService { get; set; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private IFileService fileService { get; set; } = null!;
|
||||||
|
|
||||||
|
private IQueryable<BankTransaction>? bankTransactionsQueryable;
|
||||||
|
|
||||||
|
FluentInputFile? myFileByBuffer;
|
||||||
|
int? progressPercent;
|
||||||
|
string? progressTitle;
|
||||||
|
bool IsCanceled;
|
||||||
|
Dictionary<int, string> Files = new();
|
||||||
|
|
||||||
protected override Task OnInitializedAsync()
|
protected override Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
bankTransactions = viewService.GetOpenBankTransactionList();
|
bankTransactionsQueryable = viewService.GetOpenBankTransactionList().AsQueryable();
|
||||||
return base.OnInitializedAsync();
|
return base.OnInitializedAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async Task OnProgressChangeAsync(FluentInputFileEventArgs file)
|
||||||
|
{
|
||||||
|
progressPercent = file.ProgressPercent;
|
||||||
|
progressTitle = file.ProgressTitle;
|
||||||
|
|
||||||
|
// To cancel?
|
||||||
|
file.IsCancelled = IsCanceled;
|
||||||
|
|
||||||
|
// New file
|
||||||
|
if (!Files.ContainsKey(file.Index))
|
||||||
|
{
|
||||||
|
var localFile = Path.GetTempFileName() + file.Name;
|
||||||
|
Files.Add(file.Index, localFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to the FileStream
|
||||||
|
await file.Buffer.AppendToFileAsync(Files[file.Index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnCompletedAsync(IEnumerable<FluentInputFileEventArgs> files)
|
||||||
|
{
|
||||||
|
progressPercent = myFileByBuffer!.ProgressPercent;
|
||||||
|
progressTitle = myFileByBuffer!.ProgressTitle;
|
||||||
|
|
||||||
|
foreach (var file in Files)
|
||||||
|
{
|
||||||
|
var extension = Path.GetExtension(file.Value);
|
||||||
|
if (extension is ".xml")
|
||||||
|
{
|
||||||
|
await ImportXmlAndShowResultAsync(file.Value);
|
||||||
|
}
|
||||||
|
else if (extension is ".zip")
|
||||||
|
{
|
||||||
|
var unzippedFiles = UnzipAndListFiles(file.Value);
|
||||||
|
foreach (var unzippedFile in unzippedFiles)
|
||||||
|
{
|
||||||
|
await ImportXmlAndShowResultAsync(unzippedFile);
|
||||||
|
File.Delete(unzippedFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File.Delete(file.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bankTransactionsQueryable = viewService.GetOpenBankTransactionList().AsQueryable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ImportXmlAndShowResultAsync(string fileName)
|
||||||
|
{
|
||||||
|
var messages = fileService.ImportAccountStatementFile(fileName);
|
||||||
|
foreach (var message in messages)
|
||||||
|
{
|
||||||
|
await ShowImportResultAsync(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<string> UnzipAndListFiles(string zipFilePath)
|
||||||
|
{
|
||||||
|
var tempExtractPath = Path.Combine(Path.GetTempPath(), Path.GetFileNameWithoutExtension(Path.GetRandomFileName()));
|
||||||
|
Directory.CreateDirectory(tempExtractPath);
|
||||||
|
ZipFile.ExtractToDirectory(zipFilePath, tempExtractPath);
|
||||||
|
return new List<string>(Directory.GetFiles(tempExtractPath, "*", SearchOption.AllDirectories));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ShowImportResultAsync(MessageDataset message)
|
||||||
|
{
|
||||||
|
var joinedDetails = string.Join(", ", message.Lines);
|
||||||
|
var text = $"{message.Title}: {joinedDetails}";
|
||||||
|
|
||||||
|
if (message.Status == MessageStatus.Success)
|
||||||
|
{
|
||||||
|
ShowToast(text, ToastIntent.Success);
|
||||||
|
}
|
||||||
|
else if (message.Status == MessageStatus.Info)
|
||||||
|
{
|
||||||
|
ShowToast(text, ToastIntent.Info);
|
||||||
|
}
|
||||||
|
else if (message.Status == MessageStatus.Warning)
|
||||||
|
{
|
||||||
|
ShowToast(text, ToastIntent.Warning);
|
||||||
|
}
|
||||||
|
else if (message.Status == MessageStatus.Error)
|
||||||
|
{
|
||||||
|
await ShowMessageAsync(text, MessageIntent.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ShowMessageAsync(string message, MessageIntent intent)
|
||||||
|
{
|
||||||
|
await messageService.ShowMessageBarAsync(message, intent, "MESSAGES_TOP");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowToast(string message, ToastIntent intent)
|
||||||
|
{
|
||||||
|
toastService.ShowToast(intent, message, 3000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue