Add transaction split dialog
This commit is contained in:
parent
e76644a122
commit
c86edfdde2
14 changed files with 276 additions and 33 deletions
|
|
@ -3,7 +3,7 @@ using Schaad.Accounting.Models;
|
||||||
|
|
||||||
namespace Schaad.Accounting.Datasets
|
namespace Schaad.Accounting.Datasets
|
||||||
{
|
{
|
||||||
public record TransactionDataset : Transaction
|
public class TransactionDataset : Transaction
|
||||||
{
|
{
|
||||||
public Account OriginAccount { get; set; }
|
public Account OriginAccount { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ using System.Linq;
|
||||||
|
|
||||||
namespace Schaad.Accounting.Models
|
namespace Schaad.Accounting.Models
|
||||||
{
|
{
|
||||||
public record Transaction
|
public class Transaction
|
||||||
{
|
{
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string BankTransactionId { get; set; }
|
public string BankTransactionId { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ namespace Schaad.Accounting.Repositories
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SaveTransaction(Transaction transaction)
|
public void SaveTransaction(Transaction transaction)
|
||||||
{
|
{
|
||||||
|
transaction.Value = Math.Abs(transaction.Value);
|
||||||
|
|
||||||
// prevent saving fxrate for chf account
|
// prevent saving fxrate for chf account
|
||||||
var isFxAccount = accountRepository.GetAccount(transaction.OriginAccountId).IsFxAccount;
|
var isFxAccount = accountRepository.GetAccount(transaction.OriginAccountId).IsFxAccount;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ namespace Schaad.Accounting.Services
|
||||||
|
|
||||||
if (account.Class == ClassIds.Activa && accountId == t.OriginAccountId )
|
if (account.Class == ClassIds.Activa && accountId == t.OriginAccountId )
|
||||||
{
|
{
|
||||||
return t with { Value = -t.Value };
|
t.Value *= -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return t;
|
return t;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<select @bind="@AccountId" @oninput="OnInputChanged">
|
||||||
|
<option value=""></option>
|
||||||
|
@foreach (var account in Accounts.OrderBy(a => a.Number).ThenBy(a => a.Name))
|
||||||
|
{
|
||||||
|
<option value="@account.Id">@($"{account.Number} / {account.Name}")</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Schaad.Accounting.Models;
|
||||||
|
|
||||||
|
namespace Schaad.Accounting.UI.Components.Pages.Controls;
|
||||||
|
|
||||||
|
public partial class AccountSelector : ComponentBase
|
||||||
|
{
|
||||||
|
[Parameter] public required string? AccountId { get; set; }
|
||||||
|
|
||||||
|
[Parameter] public required IReadOnlyList<Account> Accounts { get; set; }
|
||||||
|
|
||||||
|
[Parameter] public EventCallback<string> AccountIdChanged { get; set; }
|
||||||
|
|
||||||
|
private async Task OnInputChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
AccountId = e.Value?.ToString();
|
||||||
|
await AccountIdChanged.InvokeAsync(AccountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
height: 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
@implements IDialogContentComponent<Transaction>
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
|
||||||
|
<FluentDialogHeader ShowDismiss="true">
|
||||||
|
<FluentStack VerticalAlignment="VerticalAlignment.Center">
|
||||||
|
<FluentIcon Value="@(new Icons.Regular.Size24.WindowApps())"/>
|
||||||
|
<FluentLabel Typo="Typography.PaneHeader">
|
||||||
|
@Dialog.Instance.Parameters.Title
|
||||||
|
</FluentLabel>
|
||||||
|
</FluentStack>
|
||||||
|
</FluentDialogHeader>
|
||||||
|
|
||||||
|
<FluentDialogBody>
|
||||||
|
Total: @Math.Abs(Content.Value) / Rest: @openAmount
|
||||||
|
<FluentButton Appearance="Appearance.Neutral" IconEnd="@(new Icons.Regular.Size24.Add())"
|
||||||
|
OnClick="@AddTransactionAsync">
|
||||||
|
Transaktion hinzufügen
|
||||||
|
</FluentButton>
|
||||||
|
<EditForm EditContext="@editContext">
|
||||||
|
<DataAnnotationsValidator/>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Datum</th>
|
||||||
|
<th>Betrag</th>
|
||||||
|
<th>Text</th>
|
||||||
|
<th>Konto</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var transaction in transactionList)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div>@transaction.ValueDate.ToString("dd.MM.yyyy")</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>
|
||||||
|
<InputNumber @bind-Value="@transaction.Value" @onblur="CalculateOpenAmount"/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="text-column">
|
||||||
|
<input list="texts" @bind="@transaction.Text" placeholder="Type or select..."/>
|
||||||
|
<datalist id="texts">
|
||||||
|
@foreach (var text in bookingTexts.OrderBy(t => t))
|
||||||
|
{
|
||||||
|
<option value="@text"></option>
|
||||||
|
}
|
||||||
|
</datalist>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="account-column">
|
||||||
|
<AccountSelector Accounts="accounts" @bind-AccountId="@transaction.TargetAccountId" />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style="align-content: center">
|
||||||
|
<FluentButton IconEnd="@(new Icons.Regular.Size16.Delete())" Appearance="Appearance.Neutral"
|
||||||
|
OnClick="@(() => transactionList.Remove(transaction))"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div>
|
||||||
|
<FluentValidationSummary/>
|
||||||
|
</div>
|
||||||
|
</EditForm>
|
||||||
|
</FluentDialogBody>
|
||||||
|
|
||||||
|
<FluentDialogFooter>
|
||||||
|
<FluentButton Appearance="Appearance.Neutral"
|
||||||
|
OnClick="@CancelAsync">
|
||||||
|
Abbrechen
|
||||||
|
</FluentButton>
|
||||||
|
<FluentButton Appearance="Appearance.Accent" Disabled="@(openAmount != 0)"
|
||||||
|
OnClick="@SaveAsync">
|
||||||
|
Speichern
|
||||||
|
</FluentButton>
|
||||||
|
</FluentDialogFooter>
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Forms;
|
||||||
|
using Microsoft.FluentUI.AspNetCore.Components;
|
||||||
|
using Schaad.Accounting.Datasets;
|
||||||
|
using Schaad.Accounting.Interfaces;
|
||||||
|
using Schaad.Accounting.Models;
|
||||||
|
|
||||||
|
namespace Schaad.Accounting.UI.Components.Pages.Dialogs;
|
||||||
|
|
||||||
|
public partial class TransactionSplitDialog : ComponentBase
|
||||||
|
{
|
||||||
|
private EditContext editContext = null!;
|
||||||
|
|
||||||
|
[CascadingParameter]
|
||||||
|
public FluentDialog Dialog { get; set; } = null!;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public Transaction Content { get; set; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private ITransactionRepository transactionRepository { get; set; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private IBankTransactionRepository bankTransactionRepository { get; set; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private IAccountRepository accountRepository { get; set; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private IBookingTextRepository bookingTextRepository { get; set; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private IViewService viewService { get; set; } = null!;
|
||||||
|
|
||||||
|
private List<Transaction> transactionList = new();
|
||||||
|
private IReadOnlyList<AccountDataset> accounts = null!;
|
||||||
|
private IReadOnlyList<string> bookingTexts = [];
|
||||||
|
private decimal openAmount = 0;
|
||||||
|
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
accounts = viewService.GetAccountViewList();
|
||||||
|
bookingTexts = bookingTextRepository.GetBookingTextList().Select(b => b.Text).ToArray();
|
||||||
|
var accountList = accountRepository.GetAccountList();
|
||||||
|
var bankTrx = bankTransactionRepository.GetBankTransaction(Content.BankTransactionId);
|
||||||
|
var trx = new Transaction(bankTrx, accountList);
|
||||||
|
trx.Value = Math.Abs(bankTrx.Value);
|
||||||
|
transactionList.Add(trx);
|
||||||
|
editContext = new EditContext(trx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddTransactionAsync()
|
||||||
|
{
|
||||||
|
transactionList.Add(new Transaction
|
||||||
|
{
|
||||||
|
ValueDate = Content.ValueDate,
|
||||||
|
BookingDate = Content.BookingDate,
|
||||||
|
OriginAccountId = Content.OriginAccountId,
|
||||||
|
Value = 0
|
||||||
|
});
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CalculateOpenAmount()
|
||||||
|
{
|
||||||
|
openAmount = Content.Value + transactionList.Sum(t => t.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveAsync()
|
||||||
|
{
|
||||||
|
if (editContext.Validate())
|
||||||
|
{
|
||||||
|
foreach (var transtaction in transactionList)
|
||||||
|
{
|
||||||
|
transactionRepository.SaveTransaction(transtaction);
|
||||||
|
}
|
||||||
|
await Dialog.CloseAsync(Content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CancelAsync()
|
||||||
|
{
|
||||||
|
await Dialog.CancelAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
th:nth-child(1), td:nth-child(1) { width: 100px; }
|
||||||
|
th:nth-child(2), td:nth-child(2) { width: 50px; }
|
||||||
|
th:nth-child(3), td:nth-child(3) { width: 200px; }
|
||||||
|
th:nth-child(4), td:nth-child(4) { width: 200px; }
|
||||||
|
th:nth-child(5), td:nth-child(5) { width: 40px; text-align: center; }
|
||||||
|
|
||||||
|
.text-column input {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
@ -12,18 +12,20 @@
|
||||||
MaximumFileSize="@(10 * 1024 * 1024)"
|
MaximumFileSize="@(10 * 1024 * 1024)"
|
||||||
Accept=".zip, .xml"
|
Accept=".zip, .xml"
|
||||||
OnProgressChange="@OnProgressChangeAsync"
|
OnProgressChange="@OnProgressChangeAsync"
|
||||||
OnCompleted="@OnCompletedAsync" />
|
OnCompleted="@OnCompletedAsync"/>
|
||||||
|
|
||||||
<FluentProgress Visible="@(progressPercent > 0)" Min="0" Max="100" Value="@progressPercent" />
|
<FluentProgress Visible="@(progressPercent > 0)" Min="0" Max="100" Value="@progressPercent"/>
|
||||||
<FluentLabel Alignment="HorizontalAlignment.Center">
|
<FluentLabel Alignment="HorizontalAlignment.Center">
|
||||||
@progressTitle
|
@progressTitle
|
||||||
</FluentLabel>
|
</FluentLabel>
|
||||||
|
|
||||||
<FluentButton Appearance="Appearance.Lightweight" IconStart="@(new Icons.Regular.Size16.ArrowUpload())" Id="MyUploadBuffer">
|
<FluentButton Appearance="Appearance.Lightweight" IconStart="@(new Icons.Regular.Size16.ArrowUpload())"
|
||||||
|
Id="MyUploadBuffer">
|
||||||
Upload files
|
Upload files
|
||||||
</FluentButton>
|
</FluentButton>
|
||||||
|
|
||||||
<FluentButton Appearance="Appearance.Lightweight" IconStart="@(new Icons.Regular.Size16.Dismiss())" @onclick="@((e) => IsCanceled = true)">
|
<FluentButton Appearance="Appearance.Lightweight" IconStart="@(new Icons.Regular.Size16.Dismiss())"
|
||||||
|
@onclick="@((e) => IsCanceled = true)">
|
||||||
Cancel
|
Cancel
|
||||||
</FluentButton>
|
</FluentButton>
|
||||||
|
|
||||||
|
|
@ -38,10 +40,10 @@ else
|
||||||
<tr>
|
<tr>
|
||||||
<th>Buchungsdatum</th>
|
<th>Buchungsdatum</th>
|
||||||
<th>Valutadatum</th>
|
<th>Valutadatum</th>
|
||||||
<th>Text</th>
|
|
||||||
<th>Betrag</th>
|
<th>Betrag</th>
|
||||||
<th>Konto</th>
|
|
||||||
<th>Zusatzinfo</th>
|
<th>Zusatzinfo</th>
|
||||||
|
<th>Text</th>
|
||||||
|
<th>Konto</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -51,9 +53,17 @@ else
|
||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<div>@transaction.BookingDate.ToString("dd.MM.yyyy")</div></td>
|
<div>@transaction.BookingDate.ToString("dd.MM.yyyy")</div>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div>@transaction.ValueDate.ToString("dd.MM.yyyy")</div></td>
|
<div>@transaction.ValueDate.ToString("dd.MM.yyyy")</div>
|
||||||
|
</td>
|
||||||
|
<td class="betrag-column">
|
||||||
|
<div>@Math.Abs(transaction.Value).ToFormattedString()</div>
|
||||||
|
</td>
|
||||||
|
<td class="zusatzinfo-column" title="@transaction.BankTransactionText">
|
||||||
|
<div>@transaction.BankTransactionText</div>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="text-column">
|
<div class="text-column">
|
||||||
<input list="texts" @bind="@transaction.Text" placeholder="Type or select..."/>
|
<input list="texts" @bind="@transaction.Text" placeholder="Type or select..."/>
|
||||||
|
|
@ -65,23 +75,24 @@ else
|
||||||
</datalist>
|
</datalist>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="betrag-column">
|
|
||||||
<div>@transaction.Value.ToFormattedString()</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<select @bind="@transaction.TargetAccountId">
|
@if (transaction.Value > 0)
|
||||||
<option value=""></option>
|
{
|
||||||
@foreach (var account in accounts.OrderBy(a => a.Number).ThenBy(a => a.Name))
|
<AccountSelector Accounts="accounts" @bind-AccountId="@transaction.OriginAccountId" />
|
||||||
{
|
}
|
||||||
<option value="@account.Id">@($"{account.Number} / {@account.Name}")</option>
|
else
|
||||||
}
|
{
|
||||||
</select>
|
<AccountSelector Accounts="accounts" @bind-AccountId="@transaction.TargetAccountId" />
|
||||||
|
}
|
||||||
</td>
|
</td>
|
||||||
<td class="zusatzinfo-column" title="@transaction.BankTransactionText">
|
<td style="align-content: center">
|
||||||
<div>@transaction.BankTransactionText</div>
|
<FluentButton IconEnd="@(new Icons.Regular.Size16.SplitVertical())" Appearance="Appearance.Neutral"
|
||||||
|
OnClick="@(() => SplitBankTransactionAsync(transaction))" Disabled="@(transaction.Value > 0)"/>
|
||||||
|
</td>
|
||||||
|
<td style="align-content: center">
|
||||||
|
<FluentButton IconEnd="@(new Icons.Regular.Size16.Save())" Appearance="Appearance.Neutral"
|
||||||
|
OnClick="@(() => BookBankTransactionAsync(transaction))"/>
|
||||||
</td>
|
</td>
|
||||||
<td style="align-content: center"><FluentButton IconEnd="@(new Icons.Regular.Size16.SplitVertical())" Appearance="Appearance.Neutral" OnClick="@(() => SplitBankTransactionAsync(transaction))"/></td>
|
|
||||||
<td style="align-content: center"><FluentButton IconEnd="@(new Icons.Regular.Size16.Save())" Appearance="Appearance.Neutral" OnClick="@(() => BookBankTransactionAsync(transaction))"/></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using Microsoft.FluentUI.AspNetCore.Components;
|
||||||
using Schaad.Accounting.Datasets;
|
using Schaad.Accounting.Datasets;
|
||||||
using Schaad.Accounting.Interfaces;
|
using Schaad.Accounting.Interfaces;
|
||||||
using Schaad.Accounting.Models;
|
using Schaad.Accounting.Models;
|
||||||
|
using Schaad.Accounting.UI.Components.Pages.Dialogs;
|
||||||
|
|
||||||
namespace Schaad.Accounting.UI.Components.Pages;
|
namespace Schaad.Accounting.UI.Components.Pages;
|
||||||
|
|
||||||
|
|
@ -19,6 +20,9 @@ public partial class Home : ComponentBase
|
||||||
[Inject]
|
[Inject]
|
||||||
private IMessageService messageService { get; set; } = null!;
|
private IMessageService messageService { get; set; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private IDialogService dialogService { get; set; } = null!;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private ISettingsService settingsService { get; set; } = null!;
|
private ISettingsService settingsService { get; set; } = null!;
|
||||||
|
|
||||||
|
|
@ -166,7 +170,20 @@ public partial class Home : ComponentBase
|
||||||
|
|
||||||
private async Task SplitBankTransactionAsync(Transaction transaction)
|
private async Task SplitBankTransactionAsync(Transaction transaction)
|
||||||
{
|
{
|
||||||
ShowToast("Split noch nicht implementiert!", ToastIntent.Warning);
|
var dialog = await dialogService.ShowDialogAsync<TransactionSplitDialog>(transaction, new DialogParameters()
|
||||||
await Task.CompletedTask;
|
{
|
||||||
|
Height = "500px",
|
||||||
|
Width = "800px",
|
||||||
|
Title = "Split Transaktion",
|
||||||
|
PreventDismissOnOverlayClick = true,
|
||||||
|
PreventScroll = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (!result.Cancelled && result.Data != null)
|
||||||
|
{
|
||||||
|
matchingBankTransactions!.RemoveAll(q => q == transaction);
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,17 @@ th {
|
||||||
background-color: #f8f8f8;
|
background-color: #f8f8f8;
|
||||||
}
|
}
|
||||||
|
|
||||||
th:nth-child(1), td:nth-child(1) { width: 120px; } /* Buchungsdatum */
|
th:nth-child(1), td:nth-child(1) { width: 120px; }
|
||||||
th:nth-child(2), td:nth-child(2) { width: 120px; } /* Valutadatum */
|
th:nth-child(2), td:nth-child(2) { width: 120px; }
|
||||||
th:nth-child(3), td:nth-child(3) { width: 240px; } /* Text */
|
th:nth-child(3), td:nth-child(3) { width: 100px; text-align: right; }
|
||||||
th:nth-child(4), td:nth-child(4) { width: 100px; text-align: right; } /* Betrag */
|
th:nth-child(5), td:nth-child(5) { width: 240px; }
|
||||||
th:nth-child(5), td:nth-child(5) { width: 300px; } /* Konto */
|
th:nth-child(6), td:nth-child(6) { width: 300px; }
|
||||||
th:nth-child(7), td:nth-child(7),
|
th:nth-child(7), td:nth-child(7),
|
||||||
th:nth-child(8), td:nth-child(8) { width: 40px; text-align: center; } /* Buttons */
|
th:nth-child(8), td:nth-child(8) { width: 40px; text-align: center; }
|
||||||
|
|
||||||
.text-column input {
|
.text-column input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 30px;
|
||||||
box-sizing: border-box; /* Ensures padding/border doesn't overflow */
|
box-sizing: border-box; /* Ensures padding/border doesn't overflow */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
@using Plotly.Blazor
|
@using Plotly.Blazor
|
||||||
@using Schaad.Accounting.UI
|
@using Schaad.Accounting.UI
|
||||||
@using Schaad.Accounting.UI.Components
|
@using Schaad.Accounting.UI.Components
|
||||||
|
@using Schaad.Accounting.UI.Components.Pages.Controls
|
||||||
@using Schaad.Accounting.Models
|
@using Schaad.Accounting.Models
|
||||||
@using Schaad.Accounting.Interfaces
|
@using Schaad.Accounting.Interfaces
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue