Add new blazor app
This commit is contained in:
parent
4c598f0827
commit
ad75c47709
155 changed files with 64093 additions and 1 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -51,4 +51,8 @@ CodeCoverage/
|
|||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
nunit-*.xml
|
||||
/AccountingWeb/.vs
|
||||
/.vs/Accounting
|
||||
/.vs/ProjectEvaluation
|
||||
/Schaad.Accounting.UI/.vs
|
||||
|
|
|
|||
13
.idea/.idea.Accounting/.idea/.gitignore
vendored
Normal file
13
.idea/.idea.Accounting/.idea/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/modules.xml
|
||||
/contentModel.xml
|
||||
/projectSettingsUpdater.xml
|
||||
/.idea.Accounting.iml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
1
.idea/.idea.Accounting/.idea/.name
Normal file
1
.idea/.idea.Accounting/.idea/.name
Normal file
|
|
@ -0,0 +1 @@
|
|||
Accounting
|
||||
8
.idea/.idea.Accounting/.idea/dictionaries/project.xml
Normal file
8
.idea/.idea.Accounting/.idea/dictionaries/project.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="project">
|
||||
<words>
|
||||
<w>schaad</w>
|
||||
<w>spendings</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
||||
4
.idea/.idea.Accounting/.idea/encodings.xml
Normal file
4
.idea/.idea.Accounting/.idea/encodings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
||||
8
.idea/.idea.Accounting/.idea/indexLayout.xml
Normal file
8
.idea/.idea.Accounting/.idea/indexLayout.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/.idea.Accounting/.idea/vcs.xml
Normal file
6
.idea/.idea.Accounting/.idea/vcs.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
43
Accounting.sln
Normal file
43
Accounting.sln
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.13.35931.197
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Schaad.Accounting.UI", "Schaad.Accounting.UI\Schaad.Accounting.UI.csproj", "{18BB46CF-FD35-6A8A-C11E-7F5405927F50}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Schaad.Accounting.Common", "Schaad.Accounting.Common\Schaad.Accounting.Common.csproj", "{ACC60040-DC4A-6690-7600-44585B2B5508}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Schaad.Accounting.Services", "Schaad.Accounting.Services\Schaad.Accounting.Services.csproj", "{0F1A55C2-5D6B-601E-2BB2-0725B5E4C7DA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Schaad.Accounting.Db", "Schaad.Accounting.Db\Schaad.Accounting.Db.csproj", "{DC3EEA5C-7075-5F02-00A2-95BCDD87BB59}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{18BB46CF-FD35-6A8A-C11E-7F5405927F50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{18BB46CF-FD35-6A8A-C11E-7F5405927F50}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{18BB46CF-FD35-6A8A-C11E-7F5405927F50}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{18BB46CF-FD35-6A8A-C11E-7F5405927F50}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{ACC60040-DC4A-6690-7600-44585B2B5508}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{ACC60040-DC4A-6690-7600-44585B2B5508}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ACC60040-DC4A-6690-7600-44585B2B5508}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{ACC60040-DC4A-6690-7600-44585B2B5508}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0F1A55C2-5D6B-601E-2BB2-0725B5E4C7DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0F1A55C2-5D6B-601E-2BB2-0725B5E4C7DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0F1A55C2-5D6B-601E-2BB2-0725B5E4C7DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0F1A55C2-5D6B-601E-2BB2-0725B5E4C7DA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DC3EEA5C-7075-5F02-00A2-95BCDD87BB59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DC3EEA5C-7075-5F02-00A2-95BCDD87BB59}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DC3EEA5C-7075-5F02-00A2-95BCDD87BB59}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DC3EEA5C-7075-5F02-00A2-95BCDD87BB59}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {9C94ED36-E7EA-455C-967A-C293DD9B4C81}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
4
Accounting.sln.DotSettings.user
Normal file
4
Accounting.sln.DotSettings.user
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<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_003AThrowHelper_002Ecs_002Fl_003AC_0021_003FUsers_003Fschaa_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F6354a7b35d7821629924d3676acd7e67a6f7f94343e0e66ec439aa2bd6ed5_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||
10
Schaad.Accounting.Common/ClassIds.cs
Normal file
10
Schaad.Accounting.Common/ClassIds.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
namespace Schaad.Accounting
|
||||
{
|
||||
public class ClassIds
|
||||
{
|
||||
public const int Activa = 1;
|
||||
public const int Passiva = 2;
|
||||
public const int Income = 3;
|
||||
public const int Expenses = 4;
|
||||
}
|
||||
}
|
||||
38
Schaad.Accounting.Common/Datasets/AccountDataset.cs
Normal file
38
Schaad.Accounting.Common/Datasets/AccountDataset.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.Datasets
|
||||
{
|
||||
public class AccountDataset : Account
|
||||
{
|
||||
public decimal Balance { get; }
|
||||
|
||||
public decimal BalanceCHF { get; }
|
||||
|
||||
public decimal StartBalanceCHF { get; }
|
||||
|
||||
public string ClassName { get; }
|
||||
|
||||
public string SubClassName { get; }
|
||||
|
||||
public AccountDataset(Account account, decimal balance, decimal balanceCHF, decimal startBalanceCHF, string className, string subClassName)
|
||||
{
|
||||
LastBankBalance = account.LastBankBalance;
|
||||
StartBalance = account.StartBalance;
|
||||
StartBalanceCHF = startBalanceCHF;
|
||||
BankAccountNumber = account.BankAccountNumber;
|
||||
Currency = account.Currency;
|
||||
Id = account.Id;
|
||||
Name = account.Name;
|
||||
Number = account.Number;
|
||||
Balance = balance;
|
||||
BalanceCHF = balanceCHF;
|
||||
ClassName = className;
|
||||
SubClassName = subClassName;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Name: {0}, Number: {1}, Balance {2}", Name, Number, Balance);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Schaad.Accounting.Common/Datasets/BankTransactionDataset.cs
Normal file
15
Schaad.Accounting.Common/Datasets/BankTransactionDataset.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace Schaad.Accounting.Datasets
|
||||
{
|
||||
public class BankTransactionDataset
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public DateTime BookingDate { get; set; }
|
||||
public DateTime ValueDate { get; set; }
|
||||
public decimal Value { get; set; }
|
||||
public string Text { get; set; }
|
||||
public string Debtor { get; set; }
|
||||
public string Creditor { get; set; }
|
||||
}
|
||||
}
|
||||
19
Schaad.Accounting.Common/Datasets/BookingRuleDataset.cs
Normal file
19
Schaad.Accounting.Common/Datasets/BookingRuleDataset.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.Datasets
|
||||
{
|
||||
public class BookingRuleDataset : BookingRule
|
||||
{
|
||||
public string Account { get; set; }
|
||||
|
||||
public BookingRuleDataset(BookingRule bookingRule, string account)
|
||||
{
|
||||
this.LookupText = bookingRule.LookupText;
|
||||
this.LookupValue = bookingRule.LookupValue;
|
||||
this.BookingText = bookingRule.BookingText;
|
||||
this.Id = bookingRule.Id;
|
||||
this.AccountId = bookingRule.AccountId;
|
||||
this.Account = account;
|
||||
}
|
||||
}
|
||||
}
|
||||
7
Schaad.Accounting.Common/Datasets/Charts/DataSerie.cs
Normal file
7
Schaad.Accounting.Common/Datasets/Charts/DataSerie.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Schaad.Accounting.Datasets.Charts
|
||||
{
|
||||
public record DataSerie(string Id, string Name, IReadOnlyList<DateOnly> X, IReadOnlyList<decimal> Y);
|
||||
}
|
||||
49
Schaad.Accounting.Common/Datasets/MessageDataset.cs
Normal file
49
Schaad.Accounting.Common/Datasets/MessageDataset.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Schaad.Accounting.Datasets
|
||||
{
|
||||
public class MessageDataset
|
||||
{
|
||||
public string Title { get; set; }
|
||||
|
||||
public List<string> Lines { get; set; }
|
||||
|
||||
public MessageStatus Status { get; set; }
|
||||
|
||||
public MessageDataset(string title, MessageStatus status = MessageStatus.Success)
|
||||
{
|
||||
Title = title;
|
||||
Status = status;
|
||||
Lines = new List<string>();
|
||||
}
|
||||
|
||||
public void Add(string message, MessageStatus status = MessageStatus.Success)
|
||||
{
|
||||
Lines.Add(message);
|
||||
if (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
|
||||
{
|
||||
Success = 0,
|
||||
Info = 1,
|
||||
Warning = 2,
|
||||
Error = 3
|
||||
}
|
||||
}
|
||||
23
Schaad.Accounting.Common/Datasets/Reports/BalanceDataset.cs
Normal file
23
Schaad.Accounting.Common/Datasets/Reports/BalanceDataset.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Schaad.Accounting.Datasets.Reports
|
||||
{
|
||||
public class BalanceDataset
|
||||
{
|
||||
public List<AccountDataset> ActivaAccountList { get; set; }
|
||||
|
||||
public List<AccountDataset> PassivaAccountList { get; set; }
|
||||
|
||||
public decimal TotalActivaCHF { get; set; }
|
||||
|
||||
public decimal TotalPassivaCHF { get; set; }
|
||||
|
||||
public BalanceDataset(List<AccountDataset> activaAccountList, List<AccountDataset> passivaAccountList, decimal totalActivaCHF, decimal totalPassivaCHF)
|
||||
{
|
||||
ActivaAccountList = activaAccountList;
|
||||
PassivaAccountList = passivaAccountList;
|
||||
TotalActivaCHF = totalActivaCHF;
|
||||
TotalPassivaCHF = totalPassivaCHF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Schaad.Accounting.Datasets.Reports
|
||||
{
|
||||
public class BalanceSheetDataset
|
||||
{
|
||||
public List<AccountDataset> ActivaAccountList { get; }
|
||||
|
||||
public List<AccountDataset> IncomeAccountList { get; }
|
||||
|
||||
public List<AccountDataset> ExpensesAccountList { get; }
|
||||
|
||||
public decimal ProfitCHF { get; }
|
||||
|
||||
public decimal LossCHF { get; }
|
||||
|
||||
public int Year { get; }
|
||||
|
||||
public BalanceSheetDataset(List<AccountDataset> activaAccountList, List<AccountDataset> incomeAccountList, List<AccountDataset> expensesAccountList, decimal profitCHF, decimal lossCHF, int year)
|
||||
{
|
||||
ActivaAccountList = activaAccountList;
|
||||
IncomeAccountList = incomeAccountList;
|
||||
ExpensesAccountList = expensesAccountList;
|
||||
ProfitCHF = profitCHF;
|
||||
LossCHF = lossCHF;
|
||||
Year = year;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Schaad.Accounting.Common/Datasets/SettingsDataset.cs
Normal file
9
Schaad.Accounting.Common/Datasets/SettingsDataset.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace Schaad.Accounting.Datasets
|
||||
{
|
||||
public class SettingsDataset
|
||||
{
|
||||
public string DataPath { get; set; }
|
||||
|
||||
public string FixerIoApiKey { get; set; }
|
||||
}
|
||||
}
|
||||
37
Schaad.Accounting.Common/Datasets/TransactionDataset.cs
Normal file
37
Schaad.Accounting.Common/Datasets/TransactionDataset.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.Datasets
|
||||
{
|
||||
public class TransactionDataset : Transaction
|
||||
{
|
||||
public Account OriginAccount { get; set; }
|
||||
|
||||
public Account TargetAccount { get; set; }
|
||||
|
||||
public TransactionDataset(Transaction transaction, Account originAccount, Account targetAccout)
|
||||
{
|
||||
this.BankTransactionId = transaction.BankTransactionId;
|
||||
this.Id = transaction.Id;
|
||||
this.OriginAccountId = transaction.OriginAccountId;
|
||||
this.TargetAccountId = transaction.TargetAccountId;
|
||||
this.Text = transaction.Text;
|
||||
this.Value = transaction.Value;
|
||||
this.FxRate = transaction.FxRate;
|
||||
this.ValueDate = transaction.ValueDate;
|
||||
this.BookingDate = transaction.BookingDate;
|
||||
this.OriginAccount = originAccount;
|
||||
this.TargetAccount = targetAccout;
|
||||
}
|
||||
|
||||
public decimal GetValue(bool withFxRate)
|
||||
{
|
||||
if (withFxRate && FxRate != 0)
|
||||
{
|
||||
return Math.Round(Value / FxRate.Value, 2);
|
||||
}
|
||||
else
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace Schaad.Accounting.Common.Extensions
|
||||
{
|
||||
public static class DecimalExtension
|
||||
{
|
||||
public static string ToFormattedString(this decimal value)
|
||||
{
|
||||
var culture = new CultureInfo("de-CH");
|
||||
culture.NumberFormat.NumberGroupSeparator = "'";
|
||||
return value.ToString("#,0.00", culture);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Schaad.Accounting.Common/Interfaces/IAccountRepository.cs
Normal file
20
Schaad.Accounting.Common/Interfaces/IAccountRepository.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using System.Collections.Generic;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.Interfaces
|
||||
{
|
||||
public interface IAccountRepository
|
||||
{
|
||||
List<Account> GetAccountList();
|
||||
|
||||
void SaveAccount(Account account);
|
||||
|
||||
Account GetAccount(string id);
|
||||
|
||||
void DeleteAccount(string id);
|
||||
|
||||
Account GetAccountForBankAccountNumber(string bankAccountNumber);
|
||||
|
||||
void SaveBankAccountBalance(string bankAccountNumber, decimal accountBalance);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using System.Collections.Generic;
|
||||
using Schaad.Accounting.Datasets;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.Interfaces
|
||||
{
|
||||
public interface IBankTransactionRepository
|
||||
{
|
||||
List<BankTransaction> GetBankTransactionList();
|
||||
|
||||
int SaveBankTransactionList(string bankAccountNumber, List<BankTransactionDataset> bankTransactions);
|
||||
|
||||
BankTransaction GetBankTransaction(string id);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using System.Collections.Generic;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.Interfaces
|
||||
{
|
||||
public interface IBookingRuleRepository
|
||||
{
|
||||
List<BookingRule> GetBookingRuleList();
|
||||
|
||||
void SaveBookingRule(BookingRule bookingRule);
|
||||
|
||||
BookingRule GetBookingRule(string id);
|
||||
void DeleteBookingRule(string id);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.Interfaces
|
||||
{
|
||||
public interface IBookingTextRepository
|
||||
{
|
||||
List<BookingText> GetBookingTextList();
|
||||
|
||||
void SaveBookingText(BookingText bookingText);
|
||||
|
||||
BookingText GetBookingText(string id);
|
||||
|
||||
void DeleteBookingText(string id);
|
||||
}
|
||||
}
|
||||
10
Schaad.Accounting.Common/Interfaces/IChartService.cs
Normal file
10
Schaad.Accounting.Common/Interfaces/IChartService.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using Schaad.Accounting.Datasets.Charts;
|
||||
|
||||
namespace Schaad.Accounting.Interfaces
|
||||
{
|
||||
public interface IChartService
|
||||
{
|
||||
IReadOnlyList<DataSerie> GetExpensesPerMonth();
|
||||
}
|
||||
}
|
||||
15
Schaad.Accounting.Common/Interfaces/IFileService.cs
Normal file
15
Schaad.Accounting.Common/Interfaces/IFileService.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using System.Collections.Generic;
|
||||
using Schaad.Accounting.Datasets;
|
||||
using Schaad.Finance.Api;
|
||||
using Schaad.Finance.Api.Datasets;
|
||||
|
||||
namespace Schaad.Accounting.Interfaces
|
||||
{
|
||||
public interface IFileService
|
||||
{
|
||||
string Backup();
|
||||
IReadOnlyList<MessageDataset> ImportAccountStatementFile(string filePath);
|
||||
IReadOnlyList<CreditCardTransaction> ImportCreditCardStatementFile(CreditCardProvider creditCardProvider, string filePath);
|
||||
byte[] GetTransactionListCsv(string accountId);
|
||||
}
|
||||
}
|
||||
33
Schaad.Accounting.Common/Interfaces/ISettingsService.cs
Normal file
33
Schaad.Accounting.Common/Interfaces/ISettingsService.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using Schaad.Accounting.Datasets;
|
||||
|
||||
namespace Schaad.Accounting.Interfaces
|
||||
{
|
||||
public interface ISettingsService
|
||||
{
|
||||
int GetYear();
|
||||
|
||||
void SetYear(int year);
|
||||
|
||||
bool TrySetYear(int year);
|
||||
|
||||
string GetMandator();
|
||||
|
||||
void SetMandator(string mandator);
|
||||
|
||||
string GetYearMandatorPath();
|
||||
|
||||
string GetLastYearMandatorPath();
|
||||
|
||||
string GetBackupFilePath();
|
||||
|
||||
string GetUploadPath();
|
||||
|
||||
string GetCreditCardStatementPath();
|
||||
|
||||
string GetDbPath();
|
||||
|
||||
string GetLastYearDbPath();
|
||||
|
||||
SettingsDataset GetSettings();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.Interfaces
|
||||
{
|
||||
public interface ISplitPredefinitonRepository
|
||||
{
|
||||
List<SplitPredefinition> GetSplitPredefinitionList();
|
||||
|
||||
void SaveSplitPredefinition(SplitPredefinition splitPredefinition);
|
||||
}
|
||||
}
|
||||
20
Schaad.Accounting.Common/Interfaces/ISubclassRepository.cs
Normal file
20
Schaad.Accounting.Common/Interfaces/ISubclassRepository.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using System.Collections.Generic;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.Interfaces
|
||||
{
|
||||
public interface ISubclassRepository
|
||||
{
|
||||
List<SubClass> GetSubClassList();
|
||||
|
||||
void SaveSubClass(SubClass subClass);
|
||||
|
||||
SubClass GetSubClass(string id);
|
||||
|
||||
void DeleteSubClass(string id);
|
||||
|
||||
Dictionary<int, string> GetClasses();
|
||||
|
||||
string GetClass(int classId);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
using System.Collections.Generic;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.Interfaces
|
||||
{
|
||||
public interface ITransactionRepository
|
||||
{
|
||||
List<Transaction> GetTransactionList();
|
||||
|
||||
void SaveTransaction(Transaction transaction);
|
||||
|
||||
Transaction GetTransaction(string id);
|
||||
}
|
||||
}
|
||||
31
Schaad.Accounting.Common/Interfaces/IViewService.cs
Normal file
31
Schaad.Accounting.Common/Interfaces/IViewService.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using System.Collections.Generic;
|
||||
using Schaad.Accounting.Datasets;
|
||||
using Schaad.Accounting.Datasets.Reports;
|
||||
using Schaad.Accounting.Models;
|
||||
using Schaad.Finance.Api.Datasets;
|
||||
|
||||
namespace Schaad.Accounting.Interfaces
|
||||
{
|
||||
public interface IViewService
|
||||
{
|
||||
AccountDataset GetAccountView(string id);
|
||||
|
||||
List<AccountDataset> GetAccountViewList();
|
||||
|
||||
BalanceDataset GetBalanceView();
|
||||
|
||||
BalanceSheetDataset GetBalanceSheetView(int year);
|
||||
|
||||
List<TransactionDataset> GetTransactionViewList();
|
||||
|
||||
List<TransactionDataset> GetTransactionViewList(string accountId);
|
||||
|
||||
List<BookingRuleDataset> GetBookingRuleViewList();
|
||||
|
||||
List<BankTransaction> GetOpenBankTransactionList();
|
||||
|
||||
List<Transaction> MatchOpenBankTransactions();
|
||||
|
||||
List<Transaction> MatchCreditCardTransactions(string bankTransactionId, IReadOnlyList<CreditCardTransaction> creditCardTransactions);
|
||||
}
|
||||
}
|
||||
77
Schaad.Accounting.Common/Models/Account.cs
Normal file
77
Schaad.Accounting.Common/Models/Account.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace Schaad.Accounting.Models
|
||||
{
|
||||
public class Account
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
[Display(Name = "Nummer")]
|
||||
[Required]
|
||||
public int Number { get; set; }
|
||||
|
||||
[Display(Name = "Name")]
|
||||
[Required]
|
||||
[MinLength(3)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Display(Name = "Währung")]
|
||||
[Required]
|
||||
[MinLength(3)]
|
||||
[MaxLength(3)]
|
||||
public string Currency { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
[Display(Name = "Klasse")]
|
||||
public int Class
|
||||
{
|
||||
get { return Number / 1000; }
|
||||
}
|
||||
|
||||
[XmlIgnore]
|
||||
[Display(Name = "Subklasse")]
|
||||
public int SubClass
|
||||
{
|
||||
get { return Number / 100; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If mapped to a bank account
|
||||
/// </summary>
|
||||
[Display(Name = "Bankkontonummer")]
|
||||
public string BankAccountNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Start balance for acitvas
|
||||
/// </summary>
|
||||
[Display(Name = "Start Saldo")]
|
||||
public decimal StartBalance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Last received balance from bank
|
||||
/// </summary>
|
||||
[Display(Name = "Letzter Banksaldo")]
|
||||
public decimal LastBankBalance { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
public bool IsFxAccount
|
||||
{
|
||||
get { return string.IsNullOrEmpty(Currency) == false && Currency != "CHF"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes a copy
|
||||
/// </summary>
|
||||
public void Copy(Account target)
|
||||
{
|
||||
target.LastBankBalance = LastBankBalance;
|
||||
target.StartBalance = StartBalance;
|
||||
target.BankAccountNumber = BankAccountNumber;
|
||||
target.Currency = string.IsNullOrEmpty(Currency) ? "CHF" : Currency;
|
||||
target.Id = Id;
|
||||
target.Name = Name;
|
||||
target.Number = Number;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Schaad.Accounting.Common/Models/BankTransaction.cs
Normal file
27
Schaad.Accounting.Common/Models/BankTransaction.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
|
||||
namespace Schaad.Accounting.Models
|
||||
{
|
||||
public class BankTransaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
public DateTime ValueDate { get; set; }
|
||||
|
||||
public DateTime BookingDate { get; set; }
|
||||
|
||||
public string Text { get; set; }
|
||||
|
||||
public decimal Value { get; set; }
|
||||
|
||||
public string BankAccountNumber { get; set; }
|
||||
|
||||
public string Debtor { get; set; }
|
||||
public string Creditor { get; set; }
|
||||
|
||||
public bool Ignore { get; set; }
|
||||
}
|
||||
}
|
||||
37
Schaad.Accounting.Common/Models/BookingRule.cs
Normal file
37
Schaad.Accounting.Common/Models/BookingRule.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Schaad.Accounting.Models
|
||||
{
|
||||
public class BookingRule
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
[Display(Name = "Suchtext")]
|
||||
[Required]
|
||||
public string LookupText { get; set; }
|
||||
|
||||
[Display(Name = "Suchbetrag")]
|
||||
public decimal LookupValue { get; set; }
|
||||
|
||||
[Display(Name = "Buchungstext")]
|
||||
[Required]
|
||||
public string BookingText { get; set; }
|
||||
|
||||
[Display(Name = "Konto")]
|
||||
[Required]
|
||||
public string AccountId { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Makes a copy
|
||||
/// </summary>
|
||||
public void Copy(BookingRule target)
|
||||
{
|
||||
target.LookupText = LookupText;
|
||||
target.LookupValue = LookupValue;
|
||||
target.BookingText = BookingText;
|
||||
target.Id = Id;
|
||||
target.AccountId = AccountId;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Schaad.Accounting.Common/Models/BookingText.cs
Normal file
23
Schaad.Accounting.Common/Models/BookingText.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Schaad.Accounting.Models
|
||||
{
|
||||
public class BookingText
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
[Display(Name = "Buchungstext")]
|
||||
[Required]
|
||||
[MinLength(3)]
|
||||
public string Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Makes a copy
|
||||
/// </summary>
|
||||
public void Copy(BookingText target)
|
||||
{
|
||||
target.Id = Id;
|
||||
target.Text = Text;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Schaad.Accounting.Common/Models/SplitPredefinition.cs
Normal file
25
Schaad.Accounting.Common/Models/SplitPredefinition.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
namespace Schaad.Accounting.Models
|
||||
{
|
||||
public class SplitPredefinition
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string BookingText { get; set; }
|
||||
|
||||
public decimal BookingValue { get; set; }
|
||||
|
||||
public string AccountId { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Makes a copy
|
||||
/// </summary>
|
||||
public void Copy(SplitPredefinition target)
|
||||
{
|
||||
target.BookingText = BookingText;
|
||||
target.BookingValue = BookingValue;
|
||||
target.Id = Id;
|
||||
target.AccountId = AccountId;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Schaad.Accounting.Common/Models/SubClass.cs
Normal file
28
Schaad.Accounting.Common/Models/SubClass.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Schaad.Accounting.Models
|
||||
{
|
||||
public class SubClass
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
[Display(Name = "Nummer")]
|
||||
[Required]
|
||||
public int Number { get; set; }
|
||||
|
||||
[Display(Name = "Name")]
|
||||
[Required]
|
||||
[MinLength(3)]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Makes a copy
|
||||
/// </summary>
|
||||
public void Copy(SubClass target)
|
||||
{
|
||||
target.Id = Id;
|
||||
target.Name = Name;
|
||||
target.Number = Number;
|
||||
}
|
||||
}
|
||||
}
|
||||
84
Schaad.Accounting.Common/Models/Transaction.cs
Normal file
84
Schaad.Accounting.Common/Models/Transaction.cs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
|
||||
namespace Schaad.Accounting.Models
|
||||
{
|
||||
public class Transaction
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string BankTransactionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Needed for booking rule
|
||||
/// </summary>
|
||||
public string BankTransactionText { get; set; }
|
||||
|
||||
public string RelatedParty { get; set; }
|
||||
|
||||
[Display(Name = "Valuta")]
|
||||
public DateTime ValueDate { get; set; }
|
||||
|
||||
[Display(Name = "Buchung")]
|
||||
public DateTime BookingDate { get; set; }
|
||||
|
||||
[Display(Name = "Buchungstext")]
|
||||
public string Text { get; set; }
|
||||
|
||||
[Display(Name = "Betrag")]
|
||||
public decimal Value { get; set; }
|
||||
|
||||
[Display(Name = "Habenkonto")]
|
||||
public string OriginAccountId { get; set; }
|
||||
|
||||
[Display(Name = "Sollkonto")]
|
||||
public string TargetAccountId { get; set; }
|
||||
|
||||
[Display(Name = "Währungskurs")]
|
||||
public decimal? FxRate { get; set; }
|
||||
|
||||
public Transaction()
|
||||
{
|
||||
}
|
||||
|
||||
public Transaction(BankTransaction bankTransaction, List<Account> accounts)
|
||||
{
|
||||
BankTransactionId = bankTransaction.Id;
|
||||
BankTransactionText = bankTransaction.Text;
|
||||
Value = bankTransaction.Value;
|
||||
ValueDate = bankTransaction.ValueDate;
|
||||
BookingDate = bankTransaction.BookingDate;
|
||||
|
||||
// Depending on positiv or negative value, set the account
|
||||
var bankAccount = accounts.FirstOrDefault(a => a.BankAccountNumber == bankTransaction.BankAccountNumber);
|
||||
if (Value >= 0)
|
||||
{
|
||||
TargetAccountId = bankAccount.Id;
|
||||
RelatedParty = bankTransaction.Debtor;
|
||||
}
|
||||
else
|
||||
{
|
||||
OriginAccountId = bankAccount.Id;
|
||||
RelatedParty = bankTransaction.Creditor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes a copy
|
||||
/// </summary>
|
||||
public void Copy(Transaction target)
|
||||
{
|
||||
target.BankTransactionId = BankTransactionId;
|
||||
target.BankTransactionText = BankTransactionText;
|
||||
target.Id = Id;
|
||||
target.OriginAccountId = OriginAccountId;
|
||||
target.TargetAccountId = TargetAccountId;
|
||||
target.Text = Text;
|
||||
target.Value = Value;
|
||||
target.ValueDate = ValueDate;
|
||||
target.BookingDate = BookingDate > DateTime.MinValue ? BookingDate : ValueDate;
|
||||
target.FxRate = FxRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Schaad.Accounting.Common/Schaad.Accounting.Common.csproj
Normal file
17
Schaad.Accounting.Common/Schaad.Accounting.Common.csproj
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RootNamespace>Schaad.Accounting</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Schaad.Finance.Api">
|
||||
<HintPath>..\dlls\Schaad.Finance.Api.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
114
Schaad.Accounting.Db/Repositories/AccountRepository.cs
Normal file
114
Schaad.Accounting.Db/Repositories/AccountRepository.cs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.Repositories
|
||||
{
|
||||
public class AccountRepository : BaseRepository, IAccountRepository
|
||||
{
|
||||
private readonly string ACCOUNTS = "Accounts.xml";
|
||||
|
||||
public AccountRepository(ISettingsService settingsService) : base(settingsService)
|
||||
{
|
||||
EnsureFileExisits(ACCOUNTS);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Load accounts
|
||||
/// </summary>
|
||||
public List<Account> GetAccountList()
|
||||
{
|
||||
var accounts = Load<List<Account>>(ACCOUNTS);
|
||||
return accounts ?? new List<Account>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save an account (insert/update)
|
||||
/// </summary>
|
||||
public void SaveAccount(Account account)
|
||||
{
|
||||
var accounts = GetAccountList();
|
||||
var existingAccount = accounts.FirstOrDefault(a => a.Id == account.Id);
|
||||
|
||||
if (existingAccount == null)
|
||||
{
|
||||
existingAccount = new Account();
|
||||
accounts.Add(existingAccount);
|
||||
account.Id = Guid.NewGuid().ToString();
|
||||
}
|
||||
account.Copy(existingAccount);
|
||||
Save(accounts, ACCOUNTS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get account
|
||||
/// </summary>
|
||||
public Account GetAccount(string id)
|
||||
{
|
||||
var accounts = GetAccountList();
|
||||
return accounts.FirstOrDefault(t => t.Id == id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete account
|
||||
/// </summary>
|
||||
public void DeleteAccount(string id)
|
||||
{
|
||||
var accounts = GetAccountList();
|
||||
accounts.RemoveAll(t => t.Id == id);
|
||||
Save(accounts, ACCOUNTS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get account
|
||||
/// </summary>
|
||||
public Account GetAccountForBankAccountNumber(string bankAccountNumber)
|
||||
{
|
||||
var accounts = GetAccountList();
|
||||
return accounts.SingleOrDefault(t => t.BankAccountNumber == bankAccountNumber);
|
||||
}
|
||||
|
||||
public void SaveBankAccountBalance(string bankAccountNumber, decimal accountBalance)
|
||||
{
|
||||
var account = GetAccountForBankAccountNumber(bankAccountNumber);
|
||||
account.LastBankBalance = accountBalance;
|
||||
SaveAccount(account);
|
||||
}
|
||||
|
||||
private new void EnsureFileExisits(string file)
|
||||
{
|
||||
string filePath = Path.Combine(settingsService.GetDbPath(), file);
|
||||
if (File.Exists(filePath) == false)
|
||||
{
|
||||
var lastYearFile = Path.Combine(settingsService.GetLastYearDbPath(), file);
|
||||
if (File.Exists(lastYearFile))
|
||||
File.Copy(lastYearFile, filePath);
|
||||
|
||||
// set start balance to last bank balance
|
||||
if (file.IndexOf("Accounts") > -1)
|
||||
{
|
||||
var accounts = GetAccountList();
|
||||
foreach (var account in accounts.Where(a => a.Class == ClassIds.Activa))
|
||||
{
|
||||
// bank accounts
|
||||
if (account.LastBankBalance > 0)
|
||||
{
|
||||
account.StartBalance = account.LastBankBalance;
|
||||
SaveAccount(account);
|
||||
}
|
||||
// cash accounts
|
||||
else if (account.Number < 1010)
|
||||
{
|
||||
account.StartBalance = 0;
|
||||
SaveAccount(account);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Schaad.Accounting.Datasets;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.Repositories
|
||||
{
|
||||
public class BankTransactionRepository : BaseRepository, IBankTransactionRepository
|
||||
{
|
||||
private readonly string BANK_TRANSACTIONS = "BankTransactions.xml";
|
||||
|
||||
public BankTransactionRepository(ISettingsService settingsService) : base(settingsService)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load bank transactions
|
||||
/// </summary>
|
||||
public List<BankTransaction> GetBankTransactionList()
|
||||
{
|
||||
var transactions = Load<List<BankTransaction>>(BANK_TRANSACTIONS);
|
||||
return transactions != null ? transactions : new List<BankTransaction>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save new bank transactions, ignore existing ones (no update)
|
||||
/// </summary>
|
||||
public int SaveBankTransactionList(string bankAccountNumber, List<BankTransactionDataset> bankTransactions)
|
||||
{
|
||||
var count = 0;
|
||||
var transactions = GetBankTransactionList();
|
||||
foreach (var bankTrx in bankTransactions)
|
||||
{
|
||||
var transaction = transactions.FirstOrDefault(t => t.Id == bankTrx.Id);
|
||||
if (transaction == null)
|
||||
{
|
||||
transaction = new BankTransaction()
|
||||
{
|
||||
Id = bankTrx.Id,
|
||||
BookingDate = bankTrx.BookingDate,
|
||||
ValueDate = bankTrx.ValueDate,
|
||||
Value = (decimal)bankTrx.Value,
|
||||
Text = bankTrx.Text,
|
||||
BankAccountNumber = bankAccountNumber,
|
||||
Debtor = bankTrx.Debtor,
|
||||
Creditor = bankTrx.Creditor,
|
||||
};
|
||||
transactions.Add(transaction);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
Save(transactions, BANK_TRANSACTIONS);
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get bank transaction
|
||||
/// </summary>
|
||||
public BankTransaction GetBankTransaction(string id)
|
||||
{
|
||||
var transactions = GetBankTransactionList();
|
||||
return transactions.FirstOrDefault(t => t.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
72
Schaad.Accounting.Db/Repositories/BaseRepository.cs
Normal file
72
Schaad.Accounting.Db/Repositories/BaseRepository.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
|
||||
namespace Schaad.Accounting.Repositories
|
||||
{
|
||||
public abstract class BaseRepository
|
||||
{
|
||||
protected readonly ISettingsService settingsService;
|
||||
|
||||
protected BaseRepository(ISettingsService settingsService)
|
||||
{
|
||||
this.settingsService = settingsService;
|
||||
}
|
||||
|
||||
protected void EnsureFileExisits(string fileName)
|
||||
{
|
||||
string filePath = Path.Combine(settingsService.GetDbPath(), fileName);
|
||||
if (File.Exists(filePath) == false)
|
||||
{
|
||||
var lastYearFile = Path.Combine(settingsService.GetLastYearDbPath(), fileName);
|
||||
if (File.Exists(lastYearFile))
|
||||
{
|
||||
File.Copy(lastYearFile, filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save an object to an xml file
|
||||
/// </summary>
|
||||
protected void Save<T>(T obj, string fileName)
|
||||
{
|
||||
var filePath = Path.Combine(settingsService.GetDbPath(), fileName);
|
||||
using (var sww = new MemoryStream())
|
||||
{
|
||||
var settings = new XmlWriterSettings
|
||||
{
|
||||
Encoding = Encoding.UTF8,
|
||||
Indent = true
|
||||
};
|
||||
using (var writer = XmlWriter.Create(sww, settings))
|
||||
{
|
||||
var xsSubmit = new XmlSerializer(typeof(T));
|
||||
xsSubmit.Serialize(writer, obj);
|
||||
var xml = Encoding.UTF8.GetString(sww.ToArray());
|
||||
File.WriteAllText(filePath, xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load an object from an xml file
|
||||
/// </summary>
|
||||
protected T Load<T>(string fileName)
|
||||
{
|
||||
var filePath = Path.Combine(settingsService.GetDbPath(), fileName);
|
||||
if (File.Exists(filePath) == false)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
|
||||
using (XmlReader reader = XmlReader.Create(filePath))
|
||||
{
|
||||
var serializer = new XmlSerializer(typeof(T));
|
||||
return (T)serializer.Deserialize(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
64
Schaad.Accounting.Db/Repositories/BookingRuleRepository.cs
Normal file
64
Schaad.Accounting.Db/Repositories/BookingRuleRepository.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.Repositories
|
||||
{
|
||||
public class BookingRuleRepository : BaseRepository, IBookingRuleRepository
|
||||
{
|
||||
private readonly string BOOKING_RULES = "BookingRules.xml";
|
||||
|
||||
public BookingRuleRepository(ISettingsService settingsService) : base(settingsService)
|
||||
{
|
||||
EnsureFileExisits(BOOKING_RULES);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load booking rules
|
||||
/// </summary>
|
||||
public List<BookingRule> GetBookingRuleList()
|
||||
{
|
||||
var bookingTexts = Load<List<BookingRule>>(BOOKING_RULES);
|
||||
return bookingTexts ?? new List<BookingRule>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a booking rule (insert/update)
|
||||
/// </summary>
|
||||
public void SaveBookingRule(BookingRule bookingRule)
|
||||
{
|
||||
var bookingRules = GetBookingRuleList();
|
||||
var existingRule = bookingRules.FirstOrDefault(a => a.Id == bookingRule.Id);
|
||||
|
||||
if (existingRule == null)
|
||||
{
|
||||
existingRule = new BookingRule();
|
||||
bookingRules.Add(existingRule);
|
||||
bookingRule.Id = Guid.NewGuid().ToString();
|
||||
}
|
||||
bookingRule.Copy(existingRule);
|
||||
Save(bookingRules, BOOKING_RULES);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get booking rule
|
||||
/// </summary>
|
||||
public BookingRule GetBookingRule(string id)
|
||||
{
|
||||
var bookingRules = GetBookingRuleList();
|
||||
return bookingRules.FirstOrDefault(t => t.Id == id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete booking rule
|
||||
/// </summary>
|
||||
public void DeleteBookingRule(string id)
|
||||
{
|
||||
var bookingRules = GetBookingRuleList();
|
||||
bookingRules.RemoveAll(t => t.Id == id);
|
||||
Save(bookingRules, BOOKING_RULES);
|
||||
}
|
||||
}
|
||||
}
|
||||
64
Schaad.Accounting.Db/Repositories/BookingTextRepository.cs
Normal file
64
Schaad.Accounting.Db/Repositories/BookingTextRepository.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.Repositories
|
||||
{
|
||||
public class BookingTextRepository : BaseRepository, IBookingTextRepository
|
||||
{
|
||||
private readonly string BOOKING_TEXTS = "BookingTexts.xml";
|
||||
|
||||
public BookingTextRepository(ISettingsService settingsService) : base(settingsService)
|
||||
{
|
||||
EnsureFileExisits(BOOKING_TEXTS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load bokking texts
|
||||
/// </summary>
|
||||
public List<BookingText> GetBookingTextList()
|
||||
{
|
||||
var bookingTexts = Load<List<BookingText>>(BOOKING_TEXTS);
|
||||
return bookingTexts ?? new List<BookingText>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a booking text (insert/update)
|
||||
/// </summary>
|
||||
public void SaveBookingText(BookingText bookingText)
|
||||
{
|
||||
var bookingTexts = GetBookingTextList();
|
||||
var existingText = bookingTexts.FirstOrDefault(a => a.Id == bookingText.Id);
|
||||
|
||||
if (existingText == null)
|
||||
{
|
||||
existingText = new BookingText();
|
||||
bookingTexts.Add(existingText);
|
||||
bookingText.Id = Guid.NewGuid().ToString();
|
||||
}
|
||||
bookingText.Copy(existingText);
|
||||
Save(bookingTexts, BOOKING_TEXTS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get booking text
|
||||
/// </summary>
|
||||
public BookingText GetBookingText(string id)
|
||||
{
|
||||
var bookingTexts = GetBookingTextList();
|
||||
return bookingTexts.FirstOrDefault(t => t.Id == id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete booking text
|
||||
/// </summary>
|
||||
public void DeleteBookingText(string id)
|
||||
{
|
||||
var bookingTexts = GetBookingTextList();
|
||||
bookingTexts.RemoveAll(t => t.Id == id);
|
||||
Save(bookingTexts, BOOKING_TEXTS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.Repositories
|
||||
{
|
||||
public class SplitPredefinitonRepository : BaseRepository, ISplitPredefinitonRepository
|
||||
{
|
||||
private readonly string SPLIT_PREDEFINITION = "SplitPredefinitions.xml";
|
||||
|
||||
public SplitPredefinitonRepository(ISettingsService settingsService) : base(settingsService)
|
||||
{
|
||||
EnsureFileExisits(SPLIT_PREDEFINITION);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load booking rules
|
||||
/// </summary>
|
||||
public List<SplitPredefinition> GetSplitPredefinitionList()
|
||||
{
|
||||
var definitions = Load<List<SplitPredefinition>>(SPLIT_PREDEFINITION);
|
||||
return definitions ?? new List<SplitPredefinition>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a booking rule (insert/update)
|
||||
/// </summary>
|
||||
public void SaveSplitPredefinition(SplitPredefinition splitPredefinition)
|
||||
{
|
||||
var definitions = GetSplitPredefinitionList();
|
||||
var existingDefinition = definitions.FirstOrDefault(a => a.Id == splitPredefinition.Id);
|
||||
|
||||
if (existingDefinition == null)
|
||||
{
|
||||
existingDefinition = new SplitPredefinition();
|
||||
definitions.Add(existingDefinition);
|
||||
splitPredefinition.Id = Guid.NewGuid().ToString();
|
||||
}
|
||||
splitPredefinition.Copy(existingDefinition);
|
||||
Save(definitions, SPLIT_PREDEFINITION);
|
||||
}
|
||||
}
|
||||
}
|
||||
77
Schaad.Accounting.Db/Repositories/SubclassRepository.cs
Normal file
77
Schaad.Accounting.Db/Repositories/SubclassRepository.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.Repositories
|
||||
{
|
||||
public class SubclassRepository : BaseRepository, ISubclassRepository
|
||||
{
|
||||
private readonly Dictionary<int, string> classes = new Dictionary<int, string>();
|
||||
private readonly string SUBCLASSES = "SubClasses.xml";
|
||||
|
||||
public SubclassRepository(ISettingsService settingsService) : base(settingsService)
|
||||
{
|
||||
EnsureFileExisits(SUBCLASSES);
|
||||
|
||||
classes.Add(ClassIds.Activa, "Aktiven");
|
||||
classes.Add(ClassIds.Passiva, "Passiven");
|
||||
classes.Add(ClassIds.Income, "Einnahmen");
|
||||
classes.Add(ClassIds.Expenses, "Ausgaben");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load bokking texts
|
||||
/// </summary>
|
||||
public List<SubClass> GetSubClassList()
|
||||
{
|
||||
var subclasses = Load<List<SubClass>>(SUBCLASSES);
|
||||
return subclasses ?? new List<SubClass>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a booking text (insert/update)
|
||||
/// </summary>
|
||||
public void SaveSubClass(SubClass subClass)
|
||||
{
|
||||
var subclasses = GetSubClassList();
|
||||
var existingSubClass = subclasses.FirstOrDefault(a => a.Id == subClass.Id);
|
||||
|
||||
if (existingSubClass == null)
|
||||
{
|
||||
existingSubClass = new SubClass();
|
||||
subclasses.Add(existingSubClass);
|
||||
subClass.Id = Guid.NewGuid().ToString();
|
||||
}
|
||||
subClass.Copy(existingSubClass);
|
||||
Save(subclasses, SUBCLASSES);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get subclass
|
||||
/// </summary>
|
||||
public SubClass GetSubClass(string id)
|
||||
{
|
||||
var subclasses = GetSubClassList();
|
||||
return subclasses.FirstOrDefault(t => t.Id == id);
|
||||
}
|
||||
|
||||
public void DeleteSubClass(string id)
|
||||
{
|
||||
var subclasses = GetSubClassList();
|
||||
subclasses.RemoveAll(t => t.Id == id);
|
||||
Save(subclasses, SUBCLASSES);
|
||||
}
|
||||
|
||||
public Dictionary<int, string> GetClasses()
|
||||
{
|
||||
return classes;
|
||||
}
|
||||
|
||||
public string GetClass(int classId)
|
||||
{
|
||||
return classes[classId];
|
||||
}
|
||||
}
|
||||
}
|
||||
80
Schaad.Accounting.Db/Repositories/TransactionRepository.cs
Normal file
80
Schaad.Accounting.Db/Repositories/TransactionRepository.cs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.Repositories
|
||||
{
|
||||
public class TransactionRepository : BaseRepository, ITransactionRepository
|
||||
{
|
||||
private readonly IAccountRepository accountRepository;
|
||||
private readonly string TRANSACTIONS = "Transactions.xml";
|
||||
|
||||
public TransactionRepository(ISettingsService settingsService, IAccountRepository accountRepository) : base(settingsService)
|
||||
{
|
||||
this.accountRepository = accountRepository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get transaction list
|
||||
/// </summary>
|
||||
public List<Transaction> GetTransactionList()
|
||||
{
|
||||
var transactionList = Load<List<Transaction>>(TRANSACTIONS);
|
||||
return transactionList ?? new List<Transaction>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a transaction (insert/update)
|
||||
/// </summary>
|
||||
public void SaveTransaction(Transaction transaction)
|
||||
{
|
||||
// prevent saving fxrate for chf account
|
||||
var isFxAccount = accountRepository.GetAccount(transaction.OriginAccountId).IsFxAccount;
|
||||
|
||||
// if source account is fx account, we pass a foreign value -> calc to chf
|
||||
if (isFxAccount && transaction.FxRate != 0)
|
||||
{
|
||||
transaction.Value = transaction.Value * transaction.FxRate.Value;
|
||||
}
|
||||
|
||||
isFxAccount |= accountRepository.GetAccount(transaction.TargetAccountId).IsFxAccount;
|
||||
if (isFxAccount == false)
|
||||
{
|
||||
transaction.FxRate = null;
|
||||
}
|
||||
|
||||
|
||||
var transactionList = GetTransactionList();
|
||||
var existingTransaction = transactionList.FirstOrDefault(a => a.Id == transaction.Id);
|
||||
|
||||
if (existingTransaction == null)
|
||||
{
|
||||
existingTransaction = new Transaction();
|
||||
transactionList.Add(existingTransaction);
|
||||
transaction.Id = Guid.NewGuid().ToString();
|
||||
}
|
||||
transaction.Copy(existingTransaction);
|
||||
Save(transactionList, TRANSACTIONS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get transaction
|
||||
/// </summary>
|
||||
public Transaction GetTransaction(string id)
|
||||
{
|
||||
var transactions = GetTransactionList();
|
||||
var transaction = transactions.FirstOrDefault(t => t.Id == id);
|
||||
|
||||
// if source account is fx account, we save in chf currency -> calc to foreign currency for display
|
||||
var isFxAccount = accountRepository.GetAccount(transaction.OriginAccountId).IsFxAccount;
|
||||
if (isFxAccount && transaction.FxRate != 0)
|
||||
{
|
||||
transaction.Value = transaction.Value / transaction.FxRate.Value;
|
||||
}
|
||||
|
||||
return transaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Schaad.Accounting.Db/Schaad.Accounting.Db.csproj
Normal file
13
Schaad.Accounting.Db/Schaad.Accounting.Db.csproj
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Schaad.Accounting.Common\Schaad.Accounting.Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
130
Schaad.Accounting.Services/ChartService.cs
Normal file
130
Schaad.Accounting.Services/ChartService.cs
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Schaad.Accounting.Datasets.Charts;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
|
||||
namespace Schaad.Accounting.Services
|
||||
{
|
||||
public class ChartService : IChartService
|
||||
{
|
||||
private readonly IAccountRepository accountRepository;
|
||||
private readonly ISettingsService settingsService;
|
||||
private readonly ISubclassRepository subclassRepository;
|
||||
private readonly IViewService viewService;
|
||||
|
||||
public ChartService(
|
||||
ISettingsService settingsService,
|
||||
IViewService viewService,
|
||||
IAccountRepository accountRepository,
|
||||
ISubclassRepository subclassRepository)
|
||||
{
|
||||
this.settingsService = settingsService;
|
||||
this.viewService = viewService;
|
||||
this.accountRepository = accountRepository;
|
||||
this.subclassRepository = subclassRepository;
|
||||
}
|
||||
|
||||
public IReadOnlyList<DataSerie> GetExpensesPerMonth()
|
||||
{
|
||||
var transactions = viewService.GetTransactionViewList().Where(a => a.TargetAccount.Class == ClassIds.Expenses);
|
||||
|
||||
// More than one subclass xx
|
||||
if (transactions.Select(t => t.TargetAccount.SubClass).Distinct().Count() > 1)
|
||||
{
|
||||
return GetSubClassExpensesPerMonth();
|
||||
}
|
||||
|
||||
// only one subclass xx (z.B. Mandant Mannenbach)
|
||||
return GetAccountExpensesPerMonth();
|
||||
}
|
||||
|
||||
private List<DataSerie> GetSubClassExpensesPerMonth()
|
||||
{
|
||||
//var subclasses = subclassRepository.GetSubClassList();
|
||||
var transactions = viewService.GetTransactionViewList().Where(a => a.TargetAccount.Class == ClassIds.Expenses).ToList();
|
||||
var list = new List<DataSerie>();
|
||||
|
||||
var newestTransaction = transactions.OrderByDescending(t => t.ValueDate).FirstOrDefault();
|
||||
var maxMonth = newestTransaction?.ValueDate.Month ?? 12;
|
||||
var year = settingsService.GetYear();
|
||||
|
||||
// Group by subclass
|
||||
foreach (var grp in transactions.GroupBy(a => a.TargetAccount.SubClass).Select(a => new {Key = a.Key, List = a.ToList()}))
|
||||
{
|
||||
// Sum subclass transactions per month
|
||||
var groupedByMonth = grp.List.GroupBy(g => g.ValueDate.Month).ToDictionary(g => g.Key, g => g.ToList().Sum(s => s.Value));
|
||||
EnsureEntryForEveryMonth(groupedByMonth, maxMonth);
|
||||
|
||||
//var subClass = subclasses.FirstOrDefault(s => s.Number == grp.List.First().TargetAccount.SubClass);
|
||||
list.Add(
|
||||
new DataSerie(
|
||||
Id: grp.List.First().TargetAccount.SubClass.ToString(),
|
||||
Name: grp.List.First().TargetAccount.Name,
|
||||
X: groupedByMonth.OrderBy(g => g.Key).Select(g => new DateOnly(year, g.Key, 1)).ToList(),
|
||||
Y: groupedByMonth.OrderBy(g => g.Key).Select(g => g.Value).ToList()
|
||||
)
|
||||
);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private List<DataSerie> GetAccountExpensesPerMonth()
|
||||
{
|
||||
var list = new List<DataSerie>();
|
||||
var expensesAccounts = viewService.GetAccountViewList().Where(a => a.Class == ClassIds.Expenses);
|
||||
foreach (var account in expensesAccounts)
|
||||
{
|
||||
var serie = GetAccountExpensesPerMonth(account.Id, DateTime.Now.Year);
|
||||
if (serie != null)
|
||||
{
|
||||
list.Add(serie);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private DataSerie GetAccountExpensesPerMonth(string accountId, int year)
|
||||
{
|
||||
if (settingsService.TrySetYear(year))
|
||||
{
|
||||
var accountList = accountRepository.GetAccountList();
|
||||
var account = accountList.SingleOrDefault(a => a.Id == accountId);
|
||||
// perhaps we dont have the account for last year
|
||||
if (account == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var transactions = viewService.GetTransactionViewList().Where(t => t.TargetAccountId == accountId).ToList();
|
||||
|
||||
var newestTransaction = transactions.OrderByDescending(t => t.ValueDate).FirstOrDefault();
|
||||
var maxMonth = newestTransaction?.ValueDate.Month ?? 12;
|
||||
|
||||
var groupedByMonth = transactions.GroupBy(g => g.ValueDate.Month).ToDictionary(g => g.Key, g => g.ToList().Sum(s => s.Value));
|
||||
EnsureEntryForEveryMonth(groupedByMonth, maxMonth);
|
||||
|
||||
settingsService.SetYear(DateTime.Now.Year);
|
||||
|
||||
return new DataSerie(
|
||||
Id: accountId,
|
||||
Name: account.Name,
|
||||
X: groupedByMonth.OrderBy(g => g.Key).Select(g => new DateOnly(year, g.Key, 1)).ToList(),
|
||||
Y: groupedByMonth.OrderBy(g => g.Key).Select(g => g.Value).ToList()
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void EnsureEntryForEveryMonth(Dictionary<int, decimal> values, int maxMonth = 12)
|
||||
{
|
||||
for (int i = 1; i <= maxMonth; i++)
|
||||
{
|
||||
if (values.ContainsKey(i) == false)
|
||||
{
|
||||
values.Add(i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Schaad.Accounting.Services/DummyFxService.cs
Normal file
26
Schaad.Accounting.Services/DummyFxService.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using Schaad.Finance.Api;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Schaad.Accounting.Services
|
||||
{
|
||||
public class DummyFxService : IFxService
|
||||
{
|
||||
private List<string> currencies = ["CHF", "EUR", "USD"];
|
||||
|
||||
public decimal ConvertCurrency(decimal amount, string fromCurrency, string toCurrency, string fixerIoApiKey)
|
||||
{
|
||||
if (currencies.Contains(fromCurrency) && currencies.Contains(fromCurrency))
|
||||
{
|
||||
return amount;
|
||||
}
|
||||
|
||||
throw new NotImplementedException($"Cannot convert from {fromCurrency} to {toCurrency}");
|
||||
}
|
||||
|
||||
public decimal GetFxRate(string fromCurrency, string toCurrency, DateTime date, string fixerIoApiKey)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
150
Schaad.Accounting.Services/FileService.cs
Normal file
150
Schaad.Accounting.Services/FileService.cs
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Schaad.Accounting.Common.Extensions;
|
||||
using Schaad.Accounting.Datasets;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Finance.Api;
|
||||
using Schaad.Finance.Api.Datasets;
|
||||
|
||||
namespace Schaad.Accounting.Services
|
||||
{
|
||||
public class FileService : IFileService
|
||||
{
|
||||
private readonly IAccountRepository accountRepository;
|
||||
private readonly ITransactionRepository transactionsRepository;
|
||||
private readonly IBankTransactionRepository bankTransactionRepository;
|
||||
private readonly ISettingsService settingsService;
|
||||
private readonly IAccountStatementService accountStatementService;
|
||||
private readonly ICreditCardStatementService creditCardStatementService;
|
||||
|
||||
public FileService(
|
||||
ISettingsService settingsService,
|
||||
IAccountRepository accountRepository,
|
||||
ITransactionRepository transactionsRepository,
|
||||
IBankTransactionRepository bankTransactionRepository,
|
||||
IAccountStatementService accountStatementService,
|
||||
ICreditCardStatementService creditCardStatementService)
|
||||
{
|
||||
this.settingsService = settingsService;
|
||||
this.accountRepository = accountRepository;
|
||||
this.transactionsRepository = transactionsRepository;
|
||||
this.bankTransactionRepository = bankTransactionRepository;
|
||||
this.accountStatementService = accountStatementService;
|
||||
this.creditCardStatementService = creditCardStatementService;
|
||||
}
|
||||
|
||||
public string Backup()
|
||||
{
|
||||
var outpath = settingsService.GetBackupFilePath();
|
||||
var sourcepath = settingsService.GetYearMandatorPath();
|
||||
ZipFile.CreateFromDirectory(sourcepath, outpath);
|
||||
return $"Backup '{outpath}' erfolgreich erstellt";
|
||||
}
|
||||
|
||||
// Upload account statement file (mt940, camt053)
|
||||
// http://www.mikesdotnetting.com/article/288/asp-net-5-uploading-files-with-asp-net-mvc-6
|
||||
public IReadOnlyList<MessageDataset> ImportAccountStatementFile(string filePath)
|
||||
{
|
||||
var messages = new List<MessageDataset>();
|
||||
var accountStatementResults = accountStatementService.ReadFile(filePath, Encoding.UTF8);
|
||||
|
||||
var accountList = accountRepository.GetAccountList().Select(a => a.BankAccountNumber).ToList();
|
||||
foreach (var accountStatementResult in accountStatementResults)
|
||||
{
|
||||
var account = accountStatementResult.AccountStatement;
|
||||
|
||||
if (accountStatementResult.IsSuccess == false)
|
||||
{
|
||||
messages.Add(new MessageDataset($"Account {account.AccountNumber} NICHT importiert: {accountStatementResult.Error}", MessageStatus.Error));
|
||||
}
|
||||
|
||||
if (accountStatementResult.AccountStatement.Transactions.Any() == false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var message = new MessageDataset($"Konto {account.AccountNumber}");
|
||||
messages.Add(message);
|
||||
|
||||
// only accounts that are in select mandator
|
||||
if (accountList.Contains(account.AccountNumber))
|
||||
{
|
||||
var transactionsThisYear = account.Transactions
|
||||
.Where(t => t.BookingDate.Year == settingsService.GetYear())
|
||||
.Select(
|
||||
t => new BankTransactionDataset
|
||||
{
|
||||
Id = t.Id,
|
||||
Value = (decimal)t.Value,
|
||||
ValueDate = t.ValueDate,
|
||||
BookingDate = t.BookingDate,
|
||||
Text = t.Text,
|
||||
Debtor = t.Debtor,
|
||||
Creditor = t.Creditor
|
||||
})
|
||||
.ToList();
|
||||
var count = bankTransactionRepository.SaveBankTransactionList(account.AccountNumber, transactionsThisYear);
|
||||
accountRepository.SaveBankAccountBalance(account.AccountNumber, (decimal)account.EndBalance.Value);
|
||||
|
||||
var status = count == account.Transactions.Count ? MessageStatus.Success : MessageStatus.Info;
|
||||
message.Add($"{count} von {account.Transactions.Count} Transaktion(en) importiert.", status);
|
||||
|
||||
// not imported transactions
|
||||
var notImportedTransactions = account.Transactions.Where(t => transactionsThisYear.Select(tt => tt.Id).Contains(t.Id) == false);
|
||||
foreach (var trx in notImportedTransactions)
|
||||
{
|
||||
message.Add($"Transaktion wegen BookingDatum {trx.BookingDate} nicht importiert: {trx.Text}", MessageStatus.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
message.Add($"Falscher Mandant: {account.Transactions.Count} Transaktion(en) nicht importiert.", MessageStatus.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
public IReadOnlyList<CreditCardTransaction> ImportCreditCardStatementFile(CreditCardProvider creditCardProvider, string filePath)
|
||||
{
|
||||
var creditCardTransactions = creditCardStatementService.ReadFile(creditCardProvider, filePath, Encoding.UTF8);
|
||||
return creditCardTransactions;
|
||||
}
|
||||
|
||||
public byte[] GetTransactionListCsv(string accountId)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var account = accountRepository.GetAccount(accountId);
|
||||
var transactions = transactionsRepository.GetTransactionList()
|
||||
.Where(t => t.OriginAccountId == accountId || t.TargetAccountId == accountId)
|
||||
.OrderBy(t => t.BookingDate)
|
||||
.ThenBy(t => t.ValueDate)
|
||||
.ThenBy(t => t.Value)
|
||||
.ToList();
|
||||
var balance = account.StartBalance;
|
||||
sb.AppendLine($"Buchungsdatum;Valuta;Buchungstext;Belastung;Gutschrift;Saldo {account.Currency}");
|
||||
sb.AppendLine($";;Startsaldo;;;{balance}");
|
||||
foreach (var trx in transactions)
|
||||
{
|
||||
var credit = "";
|
||||
var debit = "";
|
||||
|
||||
if (trx.OriginAccountId == accountId)
|
||||
{
|
||||
debit = trx.Value.ToFormattedString();
|
||||
trx.Value *= -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
credit = trx.Value.ToFormattedString();
|
||||
}
|
||||
balance += trx.Value;
|
||||
sb.AppendLine($"{trx.BookingDate:dd.MM.yyyy};{trx.ValueDate:dd.MM.yyyy};{trx.Text};{debit};{credit};{balance.ToFormattedString()}");
|
||||
}
|
||||
var fileBytes = Encoding.GetEncoding("ISO-8859-1").GetBytes(sb.ToString());
|
||||
return fileBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Schaad.Accounting.Services/Schaad.Accounting.Services.csproj
Normal file
22
Schaad.Accounting.Services/Schaad.Accounting.Services.csproj
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FreeSpire.PDF" Version="10.2.0" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Schaad.Accounting.Common\Schaad.Accounting.Common.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Schaad.Finance">
|
||||
<HintPath>..\dlls\Schaad.Finance.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Schaad.Finance.Api">
|
||||
<HintPath>..\dlls\Schaad.Finance.Api.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
124
Schaad.Accounting.Services/SettingsService.cs
Normal file
124
Schaad.Accounting.Services/SettingsService.cs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Schaad.Accounting.Datasets;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
|
||||
namespace Schaad.Accounting.Services
|
||||
{
|
||||
public class SettingsService : ISettingsService
|
||||
{
|
||||
private readonly SettingsDataset settingsDataset;
|
||||
private string mandator = "Claudio Schaad";
|
||||
private int year = DateTime.Now.Year;
|
||||
|
||||
public SettingsService(SettingsDataset settingsDataset)
|
||||
{
|
||||
this.settingsDataset = settingsDataset;
|
||||
}
|
||||
|
||||
public int GetYear()
|
||||
{
|
||||
return year;
|
||||
}
|
||||
|
||||
public void SetYear(int year)
|
||||
{
|
||||
this.year = year;
|
||||
}
|
||||
|
||||
public bool TrySetYear(int year)
|
||||
{
|
||||
var oldYear = year;
|
||||
SetYear(year);
|
||||
if (Directory.GetFiles(GetDbPath()).Any() == false)
|
||||
{
|
||||
SetYear(oldYear);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public string GetMandator()
|
||||
{
|
||||
return mandator;
|
||||
}
|
||||
|
||||
public void SetMandator(string mandator)
|
||||
{
|
||||
this.mandator = mandator;
|
||||
}
|
||||
|
||||
public string GetYearMandatorPath()
|
||||
{
|
||||
var path = Path.Combine(GetSettings().DataPath, year.ToString(), mandator);
|
||||
if (Directory.Exists(path) == false)
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public string GetLastYearMandatorPath()
|
||||
{
|
||||
var path = Path.Combine(GetSettings().DataPath, (year - 1).ToString(), mandator);
|
||||
return path;
|
||||
}
|
||||
|
||||
public string GetBackupFilePath()
|
||||
{
|
||||
var backupPath = Path.Combine(GetSettings().DataPath, year.ToString(), "backup", mandator);
|
||||
if (Directory.Exists(backupPath) == false)
|
||||
{
|
||||
Directory.CreateDirectory(backupPath);
|
||||
}
|
||||
var backupFilePath = Path.Combine(backupPath, DateTime.Now.ToString("yyyyMMddHHmmss") + ".zip");
|
||||
return backupFilePath;
|
||||
}
|
||||
|
||||
public string GetUploadPath()
|
||||
{
|
||||
var uploadPath = Path.Combine(GetYearMandatorPath(), "uploads");
|
||||
if (Directory.Exists(uploadPath) == false)
|
||||
{
|
||||
Directory.CreateDirectory(uploadPath);
|
||||
}
|
||||
return uploadPath;
|
||||
}
|
||||
|
||||
public string GetCreditCardStatementPath()
|
||||
{
|
||||
var path = Path.Combine(GetUploadPath(), "creditCard");
|
||||
if (Directory.Exists(path) == false)
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public string GetDbPath()
|
||||
{
|
||||
var path = Path.Combine(GetYearMandatorPath(), "Data");
|
||||
if (Directory.Exists(path) == false)
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public string GetLastYearDbPath()
|
||||
{
|
||||
var path = Path.Combine(GetLastYearMandatorPath(), "Data");
|
||||
if (Directory.Exists(path) == false)
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public SettingsDataset GetSettings()
|
||||
{
|
||||
return settingsDataset;
|
||||
}
|
||||
}
|
||||
}
|
||||
355
Schaad.Accounting.Services/ViewService.cs
Normal file
355
Schaad.Accounting.Services/ViewService.cs
Normal file
|
|
@ -0,0 +1,355 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Schaad.Accounting.Datasets;
|
||||
using Schaad.Accounting.Datasets.Reports;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
using Schaad.Finance.Api;
|
||||
using Schaad.Finance.Api.Datasets;
|
||||
|
||||
namespace Schaad.Accounting.Services
|
||||
{
|
||||
public class ViewService : IViewService
|
||||
{
|
||||
private readonly IAccountRepository accountRepository;
|
||||
private readonly IBankTransactionRepository bankTransactionRepository;
|
||||
private readonly IBookingRuleRepository bookingRuleRepository;
|
||||
private readonly ISubclassRepository subclassRepository;
|
||||
private readonly ITransactionRepository transactionRepository;
|
||||
private readonly IFxService fxService;
|
||||
private readonly ISettingsService settingsService;
|
||||
|
||||
public ViewService(
|
||||
IAccountRepository accountRepository,
|
||||
IBankTransactionRepository bankTransactionRepository,
|
||||
ITransactionRepository transactionRepository,
|
||||
ISubclassRepository subclassRepository,
|
||||
IBookingRuleRepository bookingRuleRepository,
|
||||
IFxService fxService,
|
||||
ISettingsService settingsService)
|
||||
{
|
||||
this.accountRepository = accountRepository;
|
||||
this.bankTransactionRepository = bankTransactionRepository;
|
||||
this.transactionRepository = transactionRepository;
|
||||
this.subclassRepository = subclassRepository;
|
||||
this.bookingRuleRepository = bookingRuleRepository;
|
||||
this.fxService = fxService;
|
||||
this.settingsService = settingsService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load accounts
|
||||
/// </summary>
|
||||
public AccountDataset GetAccountView(string id)
|
||||
{
|
||||
var accounts = GetAccountViewList();
|
||||
return accounts.Single(t => t.Id == id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load accounts and calculates their balances
|
||||
/// </summary>
|
||||
public List<AccountDataset> GetAccountViewList()
|
||||
{
|
||||
var accounts = accountRepository.GetAccountList();
|
||||
var transactionList = GetTransactionViewList();
|
||||
var subClassNameByNumber = subclassRepository.GetSubClassList().ToDictionary(s => s.Number, s => s.Name);
|
||||
var settings = settingsService.GetSettings();
|
||||
|
||||
var accountList = accounts.Select(
|
||||
a =>
|
||||
new AccountDataset(
|
||||
account: a,
|
||||
balance: GetBalanceInAccountCurrency(a, transactionList),
|
||||
balanceCHF: GetCurrentBalanceInCHF(a, transactionList),
|
||||
startBalanceCHF: fxService.ConvertCurrency(a.StartBalance, a.Currency, "CHF", settings.FixerIoApiKey),
|
||||
className: subclassRepository.GetClass(a.Class),
|
||||
subClassName: subClassNameByNumber[a.SubClass]
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
return accountList.OrderBy(a => a.Number).ToList();
|
||||
}
|
||||
|
||||
public BalanceDataset GetBalanceView()
|
||||
{
|
||||
var accountList = GetAccountViewList();
|
||||
var balanceView = new BalanceDataset(
|
||||
activaAccountList: accountList.Where(m => m.Class == ClassIds.Activa).ToList(),
|
||||
passivaAccountList: accountList.Where(m => m.Class == ClassIds.Passiva).ToList(),
|
||||
totalActivaCHF: Math.Abs(accountList.Where(m => m.Class == ClassIds.Activa).Sum(m => m.BalanceCHF)),
|
||||
totalPassivaCHF: Math.Abs(accountList.Where(m => m.Class == ClassIds.Passiva).Sum(m => m.BalanceCHF))
|
||||
);
|
||||
|
||||
return balanceView;
|
||||
}
|
||||
|
||||
public BalanceSheetDataset GetBalanceSheetView(int year)
|
||||
{
|
||||
var accountList = GetAccountViewList();
|
||||
|
||||
var settings = settingsService.GetSettings();
|
||||
var profit = Math.Abs(accountList.Where(m => m.Class == ClassIds.Income).Sum(m =>m.BalanceCHF));
|
||||
profit += Math.Abs(accountList.Where(m => m.Class == ClassIds.Activa).Sum(m => fxService.ConvertCurrency(m.StartBalance, m.Currency, "CHF", settings.FixerIoApiKey)));
|
||||
var loss = Math.Abs(accountList.Where(m => m.Class == ClassIds.Expenses).Sum(m => m.BalanceCHF));
|
||||
|
||||
var balanceView = new BalanceSheetDataset(
|
||||
activaAccountList: accountList.Where(m => m.Class == ClassIds.Activa).ToList(),
|
||||
incomeAccountList: accountList.Where(m => m.Class == ClassIds.Income).ToList(),
|
||||
expensesAccountList: accountList.Where(m => m.Class == ClassIds.Expenses).ToList(),
|
||||
profitCHF: profit,
|
||||
lossCHF: loss,
|
||||
year: year
|
||||
);
|
||||
|
||||
return balanceView;
|
||||
}
|
||||
|
||||
private decimal GetBalanceInAccountCurrency(Account a, List<TransactionDataset> transactionList)
|
||||
{
|
||||
var balance = a.StartBalance
|
||||
+ transactionList.Where(t => t.TargetAccountId == a.Id).Sum(t => t.GetValue(a.IsFxAccount))
|
||||
- transactionList.Where(t => t.OriginAccountId == a.Id).Sum(t => t.GetValue(a.IsFxAccount));
|
||||
return balance;
|
||||
}
|
||||
|
||||
private decimal GetCurrentBalanceInCHF(Account a, List<TransactionDataset> transactionList)
|
||||
{
|
||||
var settings = settingsService.GetSettings();
|
||||
var balanceInAccountCurrency = GetBalanceInAccountCurrency(a, transactionList);
|
||||
var balanceInChf = fxService.ConvertCurrency(balanceInAccountCurrency, a.Currency, "CHF", settings.FixerIoApiKey);
|
||||
return balanceInChf;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get transaction list with the origin and target account for each transaction
|
||||
/// </summary>
|
||||
public List<TransactionDataset> GetTransactionViewList()
|
||||
{
|
||||
var accountList = accountRepository.GetAccountList();
|
||||
var transactionList = transactionRepository.GetTransactionList();
|
||||
|
||||
var transactionViewList = transactionList.Select(
|
||||
t =>
|
||||
new TransactionDataset(
|
||||
t,
|
||||
accountList.Single(a => a.Id == t.OriginAccountId),
|
||||
accountList.Single(a => a.Id == t.TargetAccountId)
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
return transactionViewList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get transaction list with the origin and target account for each transaction
|
||||
/// </summary>
|
||||
public List<TransactionDataset> GetTransactionViewList(string accountId)
|
||||
{
|
||||
var accountList = accountRepository.GetAccountList();
|
||||
var transactionList = transactionRepository.GetTransactionList().Where(t => t.OriginAccountId == accountId || t.TargetAccountId == accountId);
|
||||
|
||||
var transactionViewList = transactionList.Select(
|
||||
t =>
|
||||
new TransactionDataset(
|
||||
t,
|
||||
accountList.Single(a => a.Id == t.OriginAccountId),
|
||||
accountList.Single(a => a.Id == t.TargetAccountId)
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
return transactionViewList;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get booking rules with their account
|
||||
/// </summary>
|
||||
public List<BookingRuleDataset> GetBookingRuleViewList()
|
||||
{
|
||||
var accountList = accountRepository.GetAccountList();
|
||||
var bookinRuleList = bookingRuleRepository.GetBookingRuleList();
|
||||
|
||||
return bookinRuleList.Select(
|
||||
t =>
|
||||
new BookingRuleDataset(
|
||||
t,
|
||||
accountList.Single(a => a.Id == t.AccountId).Name
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load open bank transactions
|
||||
/// </summary>
|
||||
public List<BankTransaction> GetOpenBankTransactionList()
|
||||
{
|
||||
var transactions = transactionRepository
|
||||
.GetTransactionList()
|
||||
.Where(t => string.IsNullOrEmpty(t.BankTransactionId) == false)
|
||||
.Select(t => t.BankTransactionId);
|
||||
|
||||
var bankTransactions = bankTransactionRepository
|
||||
.GetBankTransactionList()
|
||||
.Where(b => b.Ignore == false && transactions.Contains(b.Id) == false)
|
||||
.ToList();
|
||||
|
||||
return bankTransactions;
|
||||
}
|
||||
|
||||
public List<Transaction> MatchOpenBankTransactions()
|
||||
{
|
||||
var newTransactionList = new List<Transaction>();
|
||||
var transactions = transactionRepository.GetTransactionList();
|
||||
var accounts = accountRepository.GetAccountList();
|
||||
var bookingRules = bookingRuleRepository.GetBookingRuleList();
|
||||
var bankTransactions = GetOpenBankTransactionList().OrderBy(t => t.ValueDate);
|
||||
foreach (var bankTransaction in bankTransactions)
|
||||
{
|
||||
var trx = new Transaction(bankTransaction, accounts);
|
||||
MatchBankTransactionByBookingRule(bankTransaction.Text, trx, bookingRules);
|
||||
MatchBankTransactionBySameAccountsLastMonth(bankTransaction, trx, transactions);
|
||||
MatchBankTransactionBySameValueLastMonth(bankTransaction, trx, transactions);
|
||||
newTransactionList.Add(trx);
|
||||
}
|
||||
return newTransactionList;
|
||||
}
|
||||
|
||||
public List<Transaction> MatchCreditCardTransactions(string bankTransactionId, IReadOnlyList<CreditCardTransaction> creditCardTransactions)
|
||||
{
|
||||
var accounts = accountRepository.GetAccountList();
|
||||
var bankTrx = bankTransactionRepository.GetBankTransaction(bankTransactionId);
|
||||
var bookingRules = bookingRuleRepository.GetBookingRuleList();
|
||||
|
||||
var trxList = new List<Transaction>();
|
||||
foreach (var creditCardTransaction in creditCardTransactions)
|
||||
{
|
||||
var trx = new Transaction(bankTrx, accounts);
|
||||
trx.Value = -1 * creditCardTransaction.Amount;
|
||||
trx.BookingDate = bankTrx.BookingDate;
|
||||
trx.ValueDate = creditCardTransaction.TransactionDate;
|
||||
|
||||
MatchBankTransactionByBookingRule(creditCardTransaction.Transaction, trx, bookingRules);
|
||||
|
||||
if (string.IsNullOrEmpty(trx.Text))
|
||||
{
|
||||
trx.Text = creditCardTransaction.Transaction;
|
||||
}
|
||||
trxList.Add(trx);
|
||||
}
|
||||
|
||||
return trxList;
|
||||
}
|
||||
|
||||
private void MatchBankTransactionByBookingRule(string transactionText, Transaction trx, List<BookingRule> bookingRules)
|
||||
{
|
||||
if (string.IsNullOrEmpty(trx.Text) == false)
|
||||
return;
|
||||
|
||||
// all booking rules that match the text
|
||||
var matchedRules = bookingRules.Where(r => transactionText.ToLowerInvariant().Contains(r.LookupText.ToLowerInvariant())).ToList();
|
||||
|
||||
// then match by value
|
||||
var matchedRule = matchedRules.FirstOrDefault(r => r.LookupValue == Math.Abs(trx.Value));
|
||||
|
||||
// if no found, take match without a value
|
||||
if (matchedRule == null)
|
||||
{
|
||||
matchedRule = matchedRules.FirstOrDefault(r => r.LookupValue == 0);
|
||||
}
|
||||
if (matchedRule != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(trx.OriginAccountId))
|
||||
{
|
||||
trx.OriginAccountId = matchedRule.AccountId;
|
||||
}
|
||||
else
|
||||
{
|
||||
trx.TargetAccountId = matchedRule.AccountId;
|
||||
}
|
||||
|
||||
trx.Text = matchedRule.BookingText;
|
||||
}
|
||||
}
|
||||
|
||||
private void MatchBankTransactionBySameAccountsLastMonth(BankTransaction bankTransaction, Transaction trx, List<Transaction> transactions)
|
||||
{
|
||||
if (string.IsNullOrEmpty(trx.Text) == false)
|
||||
return;
|
||||
|
||||
var lastMonth = trx.ValueDate.AddMonths(-1);
|
||||
var lastMonthStart = new DateTime(lastMonth.Year, lastMonth.Month, 1);
|
||||
var lastMonthEnd = lastMonthStart.AddMonths(1).AddMinutes(-1);
|
||||
|
||||
// Find all bookings for the same accounts of the last month
|
||||
var lastMonthTransactions = transactions.Where(
|
||||
t =>
|
||||
t.OriginAccountId == trx.OriginAccountId &&
|
||||
t.TargetAccountId == trx.TargetAccountId &&
|
||||
Math.Abs(t.Value) == Math.Abs(trx.Value) &&
|
||||
t.ValueDate >= lastMonthStart &&
|
||||
t.ValueDate <= lastMonthEnd)
|
||||
.ToList();
|
||||
|
||||
MatchSameOrOtherDay(trx, lastMonthTransactions);
|
||||
}
|
||||
|
||||
private void MatchBankTransactionBySameValueLastMonth(BankTransaction bankTransaction, Transaction trx, List<Transaction> transactions)
|
||||
{
|
||||
if (string.IsNullOrEmpty(trx.Text) == false)
|
||||
return;
|
||||
|
||||
var lastMonth = trx.ValueDate.AddMonths(-1);
|
||||
var lastMonthStart = new DateTime(lastMonth.Year, lastMonth.Month, 1);
|
||||
var lastMonthEnd = lastMonthStart.AddMonths(1).AddMinutes(-1);
|
||||
|
||||
// Find all bookings for the same amount of the last month
|
||||
var lastMonthTransactions = transactions.Where(
|
||||
t =>
|
||||
Math.Abs(t.Value) == Math.Abs(trx.Value) &&
|
||||
t.ValueDate >= lastMonthStart &&
|
||||
t.ValueDate <= lastMonthEnd)
|
||||
.ToList();
|
||||
|
||||
MatchSameOrOtherDay(trx, lastMonthTransactions);
|
||||
}
|
||||
|
||||
private void MatchSameOrOtherDay(Transaction trx, List<Transaction> lastMonthTransactions)
|
||||
{
|
||||
if (lastMonthTransactions.Any())
|
||||
{
|
||||
var lastMonthTransaction = lastMonthTransactions.Where(
|
||||
t =>
|
||||
t.ValueDate.Day == trx.ValueDate.Day)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (lastMonthTransaction == null)
|
||||
{
|
||||
lastMonthTransaction = lastMonthTransactions.Where(
|
||||
t =>
|
||||
t.ValueDate.Day >= trx.ValueDate.AddDays(-1).Day &&
|
||||
t.ValueDate.Day <= trx.ValueDate.AddDays(1).Day)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
if (lastMonthTransaction != null)
|
||||
{
|
||||
trx.Text = lastMonthTransaction.Text;
|
||||
if (string.IsNullOrEmpty(trx.OriginAccountId))
|
||||
{
|
||||
trx.OriginAccountId = lastMonthTransaction.OriginAccountId;
|
||||
}
|
||||
else
|
||||
{
|
||||
trx.TargetAccountId = lastMonthTransaction.TargetAccountId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Schaad.Accounting.UI/Components/App.razor
Normal file
21
Schaad.Accounting.UI/Components/App.razor
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<base href="/" />
|
||||
<link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]" />
|
||||
<link rel="stylesheet" href="@Assets["app.css"]" />
|
||||
<link rel="stylesheet" href="@Assets["Schaad.Accounting.UI.styles.css"]" />
|
||||
<ImportMap />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<HeadOutlet />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Routes />
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
29
Schaad.Accounting.UI/Components/Layout/MainLayout.razor
Normal file
29
Schaad.Accounting.UI/Components/Layout/MainLayout.razor
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
@inherits LayoutComponentBase
|
||||
|
||||
<FluentLayout>
|
||||
<FluentHeader Class="hidePrint">
|
||||
AccountingNext
|
||||
</FluentHeader>
|
||||
<FluentStack Class="main" Orientation="Orientation.Horizontal" Width="100%">
|
||||
<NavMenu />
|
||||
<FluentBodyContent Class="body-content">
|
||||
<div class="content">
|
||||
@Body
|
||||
</div>
|
||||
</FluentBodyContent>
|
||||
</FluentStack>
|
||||
|
||||
<FluentFooter Class="hidePrint">
|
||||
AccountingNext App
|
||||
<FluentSpacer/>
|
||||
Copyright 2025 by Claudio Schaad
|
||||
</FluentFooter>
|
||||
</FluentLayout>
|
||||
|
||||
<FluentDialogProvider @rendermode="InteractiveServer"/>
|
||||
|
||||
<div id="blazor-error-ui" data-nosnippet>
|
||||
An unhandled error has occurred.
|
||||
<a href="." class="reload">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
98
Schaad.Accounting.UI/Components/Layout/MainLayout.razor.css
Normal file
98
Schaad.Accounting.UI/Components/Layout/MainLayout.razor.css
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #d6d5d5;
|
||||
justify-content: flex-end;
|
||||
height: 3.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
white-space: nowrap;
|
||||
margin-left: 1.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.top-row ::deep a:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (max-width: 640.98px) {
|
||||
.top-row {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.page {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.top-row.auth ::deep a:first-child {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.top-row, article {
|
||||
padding-left: 2rem !important;
|
||||
padding-right: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
color-scheme: light only;
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
35
Schaad.Accounting.UI/Components/Layout/NavMenu.razor
Normal file
35
Schaad.Accounting.UI/Components/Layout/NavMenu.razor
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
@using Microsoft.FluentUI.AspNetCore.Components.Icons.Regular
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<!-- https://www.fluentui-blazor.net/Icon#explorer -->
|
||||
|
||||
<div class="navmenu hidePrint">
|
||||
<nav class="sitenav" aria-labelledby="main-menu">
|
||||
<FluentNavMenu Id="main-menu" Collapsible="true" Width="250" Title="Navigation menu" @bind-Expanded="expanded" CustomToggle="true">
|
||||
<FluentNavLink Href="/" Match="NavLinkMatch.All" Icon="@(new Size20.Home())" IconColor="Color.Accent">Home</FluentNavLink>
|
||||
<FluentNavLink Href="transactions" Icon="@(new Size20.NumberSymbolSquare())" IconColor="Color.Accent">Transaktionen</FluentNavLink>
|
||||
<FluentNavLink Href="banktransactions" Icon="@(new Size20.BuildingBank())" IconColor="Color.Accent">Bank Transaktionen</FluentNavLink>
|
||||
<FluentNavGroup Title="Stammdaten" Icon="@(new Size32.BookDatabase())">
|
||||
<FluentNavLink Href="bookingrules" Icon="@(new Size20.Check())" IconColor="Color.Accent">Buchungsregeln</FluentNavLink>
|
||||
<FluentNavLink Href="bookingtexts" Icon="@(new Size32.Text())" IconColor="Color.Accent">Buchungstexte</FluentNavLink>
|
||||
<FluentNavLink Href="Classes" Icon="@(new Icons.Filled.Size20.Class())" IconColor="Color.Accent">Klassen</FluentNavLink>
|
||||
<FluentNavLink Href="Accounts" Icon="@(new Size20.PersonAccounts())" IconColor="Color.Accent">Konten</FluentNavLink>
|
||||
</FluentNavGroup>
|
||||
<FluentNavGroup Title="Charts" Icon="@(new Size32.ChartMultiple())">
|
||||
<FluentNavLink Href="spendings" Icon="@(new Size32.DataPie())" IconColor="Color.Accent">Ausgaben</FluentNavLink>
|
||||
<FluentNavLink Href="SpendingsOverTime" Icon="@(new Size32.DataArea())" IconColor="Color.Accent">Zeitverlauf</FluentNavLink>
|
||||
<FluentNavLink Href="Assets" Icon="@(new Size32.DataPie())" IconColor="Color.Accent">Vermögen</FluentNavLink>
|
||||
</FluentNavGroup>
|
||||
<FluentNavGroup Title="Reports" Icon="@(new Size32.DocumentPrint())">
|
||||
<FluentNavLink Href="ProfitLossReport" Icon="@(new Size32.DataTrending())" IconColor="Color.Accent">Erfolgsrechnung</FluentNavLink>
|
||||
<FluentNavLink Href="DetailReport" Icon="@(new Size32.Feed())" IconColor="Color.Accent">Detailaufstellung</FluentNavLink>
|
||||
<FluentNavLink Href="BalanceReport" Icon="@(new Size32.Feed())" IconColor="Color.Accent">Bilanz</FluentNavLink>
|
||||
<FluentNavLink Href="BalanceSheetReport" Icon="@(new Size32.Receipt())" IconColor="Color.Accent">Jahresabschluss</FluentNavLink>
|
||||
</FluentNavGroup>
|
||||
</FluentNavMenu>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private bool expanded = true;
|
||||
}
|
||||
105
Schaad.Accounting.UI/Components/Layout/NavMenu.razor.css
Normal file
105
Schaad.Accounting.UI/Components/Layout/NavMenu.razor.css
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
.navbar-toggler {
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
width: 3.5rem;
|
||||
height: 2.5rem;
|
||||
color: white;
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 1rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.navbar-toggler:checked {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
min-height: 3.5rem;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.bi {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
margin-right: 0.75rem;
|
||||
top: -1px;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.bi-house-door-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-plus-square-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-list-nested-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
font-size: 0.9rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-item:first-of-type {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.nav-item:last-of-type {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep .nav-link {
|
||||
color: #d7d7d7;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 3rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-item ::deep a.active {
|
||||
background-color: rgba(255,255,255,0.37);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item ::deep .nav-link:hover {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-toggler:checked ~ .nav-scrollable {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.navbar-toggler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
/* Never collapse the sidebar for wide screens */
|
||||
display: block;
|
||||
|
||||
/* Allow sidebar to scroll for tall menus */
|
||||
height: calc(100vh - 3.5rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
30
Schaad.Accounting.UI/Components/Pages/Accounts.razor
Normal file
30
Schaad.Accounting.UI/Components/Pages/Accounts.razor
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
@page "/Accounts"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Konten</PageTitle>
|
||||
|
||||
<FluentButton IconStart="@(new Icons.Regular.Size16.Add())" Appearance="Appearance.Neutral" OnClick="AddAsync">Neues Konto hinzufügen</FluentButton>
|
||||
<FluentPaginator State="@pagination" SummaryTemplate="@template" />
|
||||
|
||||
<FluentDataGrid Items="@accountQueryable" Pagination="@pagination" RowSize="DataGridRowSize.Medium">
|
||||
<PropertyColumn Property="@(p => p.Class)" Sortable="true" Width="100px" />
|
||||
<PropertyColumn Property="@(p => p.SubClass)" Sortable="true" Width="140px" />
|
||||
<PropertyColumn Property="@(p => p.Number)" Sortable="true" IsDefaultSortColumn="true" Width="140px" />
|
||||
<PropertyColumn Property="@(p => p.Name)" Sortable="true" />
|
||||
<PropertyColumn Property="@(p => p.Currency)" />
|
||||
<PropertyColumn Property="@(p => p.BankAccountNumber)" />
|
||||
<PropertyColumn Property="@(p => p.StartBalance)" Format="C2"/>
|
||||
<PropertyColumn Property="@(p => p.LastBankBalance)" Format="C2" />
|
||||
<TemplateColumn Align="Align.Center" Width="50px">
|
||||
<FluentButton IconEnd="@(new Icons.Regular.Size16.Delete())" Appearance="Appearance.Neutral" OnClick="@(() => DeleteAsync(context.Id))" />
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Align="Align.Center" Width="50px">
|
||||
<FluentButton IconEnd="@(new Icons.Regular.Size16.Edit())" Appearance="Appearance.Neutral" OnClick="@(() => EditAsync(context.Id))" />
|
||||
</TemplateColumn>
|
||||
</FluentDataGrid>
|
||||
|
||||
@code
|
||||
{
|
||||
private RenderFragment template = @<span />;
|
||||
PaginationState pagination = new PaginationState() { ItemsPerPage = 20 };
|
||||
}
|
||||
72
Schaad.Accounting.UI/Components/Pages/Accounts.razor.cs
Normal file
72
Schaad.Accounting.UI/Components/Pages/Accounts.razor.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.FluentUI.AspNetCore.Components;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
using Schaad.Accounting.UI.Components.Pages.Dialogs;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages;
|
||||
|
||||
public partial class Accounts : ComponentBase
|
||||
{
|
||||
[Inject]
|
||||
private IAccountRepository accountRepository { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IDialogService dialogService { get; set; } = null!;
|
||||
|
||||
private IQueryable<Account>? accountQueryable;
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
accountQueryable = accountRepository.GetAccountList().AsQueryable();
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private async Task AddAsync()
|
||||
{
|
||||
var data = new Account();
|
||||
var dialog = await dialogService.ShowDialogAsync<AccountDialog>(data, new DialogParameters()
|
||||
{
|
||||
Height = "500px",
|
||||
Title = "Konto",
|
||||
PreventDismissOnOverlayClick = true,
|
||||
PreventScroll = true,
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result.Cancelled && result.Data != null)
|
||||
{
|
||||
accountQueryable = accountRepository.GetAccountList().AsQueryable();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EditAsync(string id)
|
||||
{
|
||||
var data = accountRepository.GetAccount(id);
|
||||
var dialog = await dialogService.ShowDialogAsync<AccountDialog>(data, new DialogParameters()
|
||||
{
|
||||
Height = "500px",
|
||||
Title = "Konto",
|
||||
PreventDismissOnOverlayClick = true,
|
||||
PreventScroll = true,
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result.Cancelled && result.Data != null)
|
||||
{
|
||||
accountQueryable = accountRepository.GetAccountList().AsQueryable();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAsync(string id)
|
||||
{
|
||||
var account = accountRepository.GetAccount(id);
|
||||
var dialog = await dialogService.ShowConfirmationAsync($"Konto '{account.Name}' wirklich löschen?", "Ja", "Nein", "Konto löschen");
|
||||
var result = await dialog.Result;
|
||||
if (!result.Cancelled)
|
||||
{
|
||||
accountRepository.DeleteAccount(id);
|
||||
accountQueryable = accountRepository.GetAccountList().AsQueryable();
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Schaad.Accounting.UI/Components/Pages/BankTransactions.razor
Normal file
19
Schaad.Accounting.UI/Components/Pages/BankTransactions.razor
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
@page "/banktransactions"
|
||||
<PageTitle>BankTransactions</PageTitle>
|
||||
|
||||
<FluentPaginator State="@pagination" />
|
||||
|
||||
<div id="datagrid-container">
|
||||
|
||||
<FluentDataGrid Items="@TransactionList" Pagination="@pagination" RowSize="@rowSize" AutoItemsPerPage="true" Style="overflow-y:hidden;">
|
||||
<PropertyColumn Title="Buchungsdatum" Width="100" Property="@(p => p.BookingDate)" Format="dd.MM.yyyy" Sortable="true" IsDefaultSortColumn="true" InitialSortDirection="SortDirection.Descending" />
|
||||
<PropertyColumn Title="Valutadatum" Width="100" Property="@(p => p.ValueDate)" Format="dd.MM.yyyy" Sortable="true" />
|
||||
<PropertyColumn Title="Text" Property="@(p => p.Text)" />
|
||||
<PropertyColumn Title="Betrag" Width="100" Property="@(p => p.Value)" Format="C2" Align="Align.End"/>
|
||||
</FluentDataGrid>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
DataGridRowSize rowSize = DataGridRowSize.Small;
|
||||
PaginationState pagination = new PaginationState() { ItemsPerPage = 10 };
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages;
|
||||
|
||||
public partial class BankTransactions : ComponentBase
|
||||
{
|
||||
[Inject]
|
||||
private IBankTransactionRepository bankTransactionRepository { get; set; } = null!;
|
||||
private IQueryable<BankTransaction>? TransactionList;
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
TransactionList = bankTransactionRepository.GetBankTransactionList().AsQueryable();
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
#datagrid-container {
|
||||
height: calc(100% - 3rem);
|
||||
min-height: 8rem;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
article {
|
||||
min-height: 32rem;
|
||||
max-height: 80dvh;
|
||||
}
|
||||
|
||||
.demo-section-content {
|
||||
height: calc(100% - 10rem);
|
||||
}
|
||||
|
||||
.demo-section-example {
|
||||
min-height: 135px !important;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
fluent-tabs {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#tab-example-autoitemsperpage-panel {
|
||||
height: 100% !important;
|
||||
max-height: calc(100% - 2rem) !important;
|
||||
}
|
||||
26
Schaad.Accounting.UI/Components/Pages/BookingRules.razor
Normal file
26
Schaad.Accounting.UI/Components/Pages/BookingRules.razor
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
@page "/bookingrules"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Buchungsregeln</PageTitle>
|
||||
|
||||
<FluentButton IconStart="@(new Icons.Regular.Size16.Add())" Appearance="Appearance.Neutral" OnClick="AddAsync">Neue Regel hinzufügen</FluentButton>
|
||||
<FluentPaginator State="@pagination" SummaryTemplate="@template" />
|
||||
|
||||
<FluentDataGrid Items="@bookingRulesQueryable" Pagination="@pagination" RowSize="DataGridRowSize.Medium">
|
||||
<PropertyColumn Property="@(p => p.LookupText)" Sortable="true" IsDefaultSortColumn="true" />
|
||||
<PropertyColumn Property="@(p => p.LookupValue)" Format="C2" Align="Align.End"/>
|
||||
<PropertyColumn Property="@(p => p.BookingText)" />
|
||||
<PropertyColumn Property="@(p => p.Account)" />
|
||||
<TemplateColumn Align="Align.Center" Width="50px">
|
||||
<FluentButton IconEnd="@(new Icons.Regular.Size16.Delete())" Appearance="Appearance.Neutral" OnClick="@(() => DeleteAsync(context.Id))" />
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Align="Align.Center" Width="50px">
|
||||
<FluentButton IconEnd="@(new Icons.Regular.Size16.Edit())" Appearance="Appearance.Neutral" OnClick="@(() => EditAsync(context.Id))" />
|
||||
</TemplateColumn>
|
||||
</FluentDataGrid>
|
||||
|
||||
@code
|
||||
{
|
||||
private RenderFragment template = @<span />;
|
||||
PaginationState pagination = new PaginationState() { ItemsPerPage = 20 };
|
||||
}
|
||||
76
Schaad.Accounting.UI/Components/Pages/BookingRules.razor.cs
Normal file
76
Schaad.Accounting.UI/Components/Pages/BookingRules.razor.cs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.FluentUI.AspNetCore.Components;
|
||||
using Schaad.Accounting.Datasets;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
using Schaad.Accounting.UI.Components.Pages.Dialogs;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages;
|
||||
|
||||
public partial class BookingRules : ComponentBase
|
||||
{
|
||||
[Inject]
|
||||
private IViewService viewService { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IBookingRuleRepository bookingRuleRepository { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IDialogService dialogService { get; set; } = null!;
|
||||
|
||||
private IQueryable<BookingRuleDataset>? bookingRulesQueryable;
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
bookingRulesQueryable = viewService.GetBookingRuleViewList().AsQueryable();
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private async Task AddAsync()
|
||||
{
|
||||
var data = new BookingRule();
|
||||
var dialog = await dialogService.ShowDialogAsync<BookingRuleDialog>(data, new DialogParameters()
|
||||
{
|
||||
Height = "500px",
|
||||
Title = "Buchungsregel",
|
||||
PreventDismissOnOverlayClick = true,
|
||||
PreventScroll = true,
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result.Cancelled && result.Data != null)
|
||||
{
|
||||
bookingRulesQueryable = viewService.GetBookingRuleViewList().AsQueryable();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EditAsync(string id)
|
||||
{
|
||||
var data = bookingRuleRepository.GetBookingRule(id);
|
||||
var dialog = await dialogService.ShowDialogAsync<BookingRuleDialog>(data, new DialogParameters()
|
||||
{
|
||||
Height = "500px",
|
||||
Title = "Buchungsregel",
|
||||
PreventDismissOnOverlayClick = true,
|
||||
PreventScroll = true,
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result.Cancelled && result.Data != null)
|
||||
{
|
||||
bookingRulesQueryable = viewService.GetBookingRuleViewList().AsQueryable();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAsync(string id)
|
||||
{
|
||||
var bookingRule = bookingRuleRepository.GetBookingRule(id);
|
||||
var dialog = await dialogService.ShowConfirmationAsync($"Regel '{bookingRule.LookupText}' wirklich löschen?", "Ja", "Nein", "Buchungsregel löschen");
|
||||
var result = await dialog.Result;
|
||||
if (!result.Cancelled)
|
||||
{
|
||||
bookingRuleRepository.DeleteBookingRule(id);
|
||||
bookingRulesQueryable = viewService.GetBookingRuleViewList().AsQueryable();
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Schaad.Accounting.UI/Components/Pages/BookingTexts.razor
Normal file
23
Schaad.Accounting.UI/Components/Pages/BookingTexts.razor
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
@page "/bookingtexts"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Buchungstexte</PageTitle>
|
||||
|
||||
<FluentButton IconStart="@(new Icons.Regular.Size16.Add())" Appearance="Appearance.Neutral" OnClick="AddAsync">Neuer Text hinzufügen</FluentButton>
|
||||
<FluentPaginator State="@pagination" SummaryTemplate="@template" />
|
||||
|
||||
<FluentDataGrid Items="@bookingTextQueryable" Pagination="@pagination" RowSize="DataGridRowSize.Medium">
|
||||
<PropertyColumn Property="@(p => p.Text)" Sortable="true" IsDefaultSortColumn="true" />
|
||||
<TemplateColumn Align="Align.Center" Width="50px">
|
||||
<FluentButton IconEnd="@(new Icons.Regular.Size16.Delete())" Appearance="Appearance.Neutral" OnClick="@(() => DeleteAsync(context.Id))" />
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Align="Align.Center" Width="50px">
|
||||
<FluentButton IconEnd="@(new Icons.Regular.Size16.Edit())" Appearance="Appearance.Neutral" OnClick="@(() => EditAsync(context.Id))" />
|
||||
</TemplateColumn>
|
||||
</FluentDataGrid>
|
||||
|
||||
@code
|
||||
{
|
||||
private RenderFragment template = @<span />;
|
||||
PaginationState pagination = new PaginationState() { ItemsPerPage = 20 };
|
||||
}
|
||||
72
Schaad.Accounting.UI/Components/Pages/BookingTexts.razor.cs
Normal file
72
Schaad.Accounting.UI/Components/Pages/BookingTexts.razor.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.FluentUI.AspNetCore.Components;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
using Schaad.Accounting.UI.Components.Pages.Dialogs;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages;
|
||||
|
||||
public partial class BookingTexts : ComponentBase
|
||||
{
|
||||
[Inject]
|
||||
private IBookingTextRepository bookingTextRepository { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IDialogService dialogService { get; set; } = null!;
|
||||
|
||||
private IQueryable<BookingText>? bookingTextQueryable;
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
bookingTextQueryable = bookingTextRepository.GetBookingTextList().AsQueryable();
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private async Task AddAsync()
|
||||
{
|
||||
var data = new BookingText();
|
||||
var dialog = await dialogService.ShowDialogAsync<BookingTextDialog>(data, new DialogParameters()
|
||||
{
|
||||
Height = "500px",
|
||||
Title = "Add Buchungstext",
|
||||
PreventDismissOnOverlayClick = true,
|
||||
PreventScroll = true,
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result.Cancelled && result.Data != null)
|
||||
{
|
||||
bookingTextQueryable = bookingTextRepository.GetBookingTextList().AsQueryable();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EditAsync(string id)
|
||||
{
|
||||
var data = bookingTextRepository.GetBookingText(id);
|
||||
var dialog = await dialogService.ShowDialogAsync<BookingTextDialog>(data, new DialogParameters()
|
||||
{
|
||||
Height = "500px",
|
||||
Title = "Edit Buchungstext",
|
||||
PreventDismissOnOverlayClick = true,
|
||||
PreventScroll = true,
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result.Cancelled && result.Data != null)
|
||||
{
|
||||
bookingTextQueryable = bookingTextRepository.GetBookingTextList().AsQueryable();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAsync(string id)
|
||||
{
|
||||
var bookingText = bookingTextRepository.GetBookingText(id);
|
||||
var dialog = await dialogService.ShowConfirmationAsync($"Buchungstext '{bookingText.Text}' wirklich löschen?", "Ja", "Nein", "Buchungstext löschen");
|
||||
var result = await dialog.Result;
|
||||
if (!result.Cancelled)
|
||||
{
|
||||
bookingTextRepository.DeleteBookingText(id);
|
||||
bookingTextQueryable = bookingTextRepository.GetBookingTextList().AsQueryable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
@page "/Assets"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Ausgaben</PageTitle>
|
||||
|
||||
<PlotlyChart style="height: 700px; min-height: 500px" @bind-Config="config" @bind-Layout="layout" @bind-Data="data" @ref="chart" />
|
||||
55
Schaad.Accounting.UI/Components/Pages/Charts/Assets.razor.cs
Normal file
55
Schaad.Accounting.UI/Components/Pages/Charts/Assets.razor.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Plotly.Blazor;
|
||||
using Plotly.Blazor.Traces;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages.Charts;
|
||||
|
||||
public partial class Assets : ComponentBase
|
||||
{
|
||||
private PlotlyChart chart = null!;
|
||||
private Config config = null!;
|
||||
private Plotly.Blazor.Layout layout = null!;
|
||||
private IList<ITrace> data = null!;
|
||||
|
||||
[Inject]
|
||||
private IViewService viewService { get; set; } = null!;
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
config = new Config
|
||||
{
|
||||
Responsive = true
|
||||
};
|
||||
|
||||
layout = new Plotly.Blazor.Layout
|
||||
{
|
||||
};
|
||||
|
||||
var accounts = viewService.GetAccountViewList().Where(a => a.Class == ClassIds.Activa);
|
||||
var values = new List<object>();
|
||||
var labels = new List<object>();
|
||||
var ids = new List<object>();
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
if (account.BalanceCHF > 0)
|
||||
{
|
||||
values.Add(account.BalanceCHF);
|
||||
labels.Add(account.Name);
|
||||
ids.Add(account.Id);
|
||||
}
|
||||
}
|
||||
|
||||
data = new List<ITrace>
|
||||
{
|
||||
new Pie
|
||||
{
|
||||
Values = values,
|
||||
Labels = labels,
|
||||
Ids = ids
|
||||
}
|
||||
};
|
||||
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
@page "/spendings"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Ausgaben</PageTitle>
|
||||
|
||||
<PlotlyChart style="height: 700px; min-height: 500px" @bind-Config="config" @bind-Layout="layout" @bind-Data="data" @ref="chart" />
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Plotly.Blazor;
|
||||
using Plotly.Blazor.Traces;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages.Charts;
|
||||
|
||||
public partial class Spendings : ComponentBase
|
||||
{
|
||||
private PlotlyChart chart = null!;
|
||||
private Config config = null!;
|
||||
private Plotly.Blazor.Layout layout = null!;
|
||||
private IList<ITrace> data = null!;
|
||||
|
||||
[Inject]
|
||||
private IViewService viewService { get; set; } = null!;
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
config = new Config
|
||||
{
|
||||
Responsive = true
|
||||
};
|
||||
|
||||
layout = new Plotly.Blazor.Layout
|
||||
{
|
||||
};
|
||||
|
||||
var accounts = viewService.GetAccountViewList().Where(a => a.Class == ClassIds.Expenses);
|
||||
var values = new List<object>();
|
||||
var labels = new List<object>();
|
||||
foreach (var grp in accounts.GroupBy(a => a.SubClass).Select(a => new {Key = a.Key, List = a.ToList()}))
|
||||
{
|
||||
if (grp.List.Sum(g => g.Balance) != 0)
|
||||
{
|
||||
values.Add(grp.List.Sum(g => g.Balance));
|
||||
labels.Add(grp.List.First().SubClassName);
|
||||
}
|
||||
}
|
||||
|
||||
data = new List<ITrace>
|
||||
{
|
||||
new Pie
|
||||
{
|
||||
Values =values,
|
||||
Labels = labels
|
||||
}
|
||||
};
|
||||
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
@page "/SpendingsOverTime"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Ausgaben im Verlauf</PageTitle>
|
||||
|
||||
<PlotlyChart style="height: 700px; min-height: 500px" @bind-Config="config" @bind-Layout="layout" @bind-Data="data" @ref="chart" />
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Plotly.Blazor;
|
||||
using Plotly.Blazor.LayoutLib;
|
||||
using Plotly.Blazor.Traces;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages.Charts;
|
||||
|
||||
public partial class SpendingsOverTime : ComponentBase
|
||||
{
|
||||
private PlotlyChart chart = null!;
|
||||
private Config config = null!;
|
||||
private Plotly.Blazor.Layout layout = null!;
|
||||
private IList<ITrace> data = new List<ITrace>();
|
||||
|
||||
[Inject]
|
||||
private IChartService chartService { get; set; } = null!;
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
config = new Config
|
||||
{
|
||||
Responsive = true
|
||||
};
|
||||
|
||||
layout = new Plotly.Blazor.Layout
|
||||
{
|
||||
Title = new Title { Text = "Ausgaben im Verlauf" },
|
||||
BarMode = BarModeEnum.Stack,
|
||||
YAxis = new List<YAxis>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Title = new Plotly.Blazor.LayoutLib.YAxisLib.Title { Text = "CHF" }
|
||||
}
|
||||
},
|
||||
XAxis = new List<XAxis>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Type = Plotly.Blazor.LayoutLib.XAxisLib.TypeEnum.Date
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var dataSeries = chartService.GetExpensesPerMonth();
|
||||
foreach (var dataSerie in dataSeries)
|
||||
{
|
||||
data.Add(new Bar
|
||||
{
|
||||
Name = dataSerie.Name,
|
||||
X = dataSerie.X.Select(d => (object)d).ToList(),
|
||||
Y = dataSerie.Y.Select(d => (object)d).ToList(),
|
||||
});
|
||||
};
|
||||
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
}
|
||||
24
Schaad.Accounting.UI/Components/Pages/Classes.razor
Normal file
24
Schaad.Accounting.UI/Components/Pages/Classes.razor
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
@page "/Classes"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Klassen</PageTitle>
|
||||
|
||||
<FluentButton IconStart="@(new Icons.Regular.Size16.Add())" Appearance="Appearance.Neutral" OnClick="AddAsync">Neue Klasse hinzufügen</FluentButton>
|
||||
<FluentPaginator State="@pagination" SummaryTemplate="@template" />
|
||||
|
||||
<FluentDataGrid Items="@subclassQueryable" Pagination="@pagination" RowSize="DataGridRowSize.Medium">
|
||||
<PropertyColumn Property="@(p => p.Number)" Sortable="true" IsDefaultSortColumn="true" Width="200px" />
|
||||
<PropertyColumn Property="@(p => p.Name)" Sortable="true" />
|
||||
<TemplateColumn Align="Align.Center" Width="50px">
|
||||
<FluentButton IconEnd="@(new Icons.Regular.Size16.Delete())" Appearance="Appearance.Neutral" OnClick="@(() => DeleteAsync(context.Id))" />
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Align="Align.Center" Width="50px">
|
||||
<FluentButton IconEnd="@(new Icons.Regular.Size16.Edit())" Appearance="Appearance.Neutral" OnClick="@(() => EditAsync(context.Id))" />
|
||||
</TemplateColumn>
|
||||
</FluentDataGrid>
|
||||
|
||||
@code
|
||||
{
|
||||
private RenderFragment template = @<span />;
|
||||
PaginationState pagination = new PaginationState() { ItemsPerPage = 20 };
|
||||
}
|
||||
72
Schaad.Accounting.UI/Components/Pages/Classes.razor.cs
Normal file
72
Schaad.Accounting.UI/Components/Pages/Classes.razor.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.FluentUI.AspNetCore.Components;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
using Schaad.Accounting.UI.Components.Pages.Dialogs;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages;
|
||||
|
||||
public partial class Classes : ComponentBase
|
||||
{
|
||||
[Inject]
|
||||
private ISubclassRepository subclassRepository { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IDialogService dialogService { get; set; } = null!;
|
||||
|
||||
private IQueryable<SubClass>? subclassQueryable;
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
subclassQueryable = subclassRepository.GetSubClassList().AsQueryable();
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private async Task AddAsync()
|
||||
{
|
||||
var data = new SubClass();
|
||||
var dialog = await dialogService.ShowDialogAsync<ClassDialog>(data, new DialogParameters()
|
||||
{
|
||||
Height = "500px",
|
||||
Title = "Klasse",
|
||||
PreventDismissOnOverlayClick = true,
|
||||
PreventScroll = true,
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result.Cancelled && result.Data != null)
|
||||
{
|
||||
subclassQueryable = subclassRepository.GetSubClassList().AsQueryable();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EditAsync(string id)
|
||||
{
|
||||
var data = subclassRepository.GetSubClass(id);
|
||||
var dialog = await dialogService.ShowDialogAsync<ClassDialog>(data, new DialogParameters()
|
||||
{
|
||||
Height = "500px",
|
||||
Title = "Klasse",
|
||||
PreventDismissOnOverlayClick = true,
|
||||
PreventScroll = true,
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result.Cancelled && result.Data != null)
|
||||
{
|
||||
subclassQueryable = subclassRepository.GetSubClassList().AsQueryable();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAsync(string id)
|
||||
{
|
||||
var subclass = subclassRepository.GetSubClass(id);
|
||||
var dialog = await dialogService.ShowConfirmationAsync($"Klasse '{subclass.Name}' wirklich löschen?", "Ja", "Nein", "Klasse löschen");
|
||||
var result = await dialog.Result;
|
||||
if (!result.Cancelled)
|
||||
{
|
||||
subclassRepository.DeleteSubClass(id);
|
||||
subclassQueryable = subclassRepository.GetSubClassList().AsQueryable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
@implements IDialogContentComponent<Schaad.Accounting.Models.Account>
|
||||
@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>
|
||||
<EditForm EditContext="@editContext">
|
||||
<DataAnnotationsValidator />
|
||||
<FluentNumberField @bind-Value="@Content.Number" Label="Nummer" Style="width: 300px;"/>
|
||||
<FluentTextField @bind-Value="@Content.Name" Label="Name" Style="width: 300px;"/>
|
||||
<FluentTextField @bind-Value="@Content.Currency" Label="Währung" Style="width: 300px;"/>
|
||||
<FluentTextField @bind-Value="@Content.BankAccountNumber" Label="Bankkonto" Style="width: 300px;"/>
|
||||
<FluentNumberField @bind-Value="@Content.StartBalance" Label="Startsaldo" Style="width: 300px;"/>
|
||||
<div>
|
||||
<FluentValidationSummary />
|
||||
</div>
|
||||
</EditForm>
|
||||
</FluentDialogBody>
|
||||
|
||||
<FluentDialogFooter>
|
||||
<FluentButton Appearance="Appearance.Neutral"
|
||||
OnClick="@CancelAsync">
|
||||
Abbrechen
|
||||
</FluentButton>
|
||||
<FluentButton Appearance="Appearance.Accent"
|
||||
OnClick="@SaveAsync">
|
||||
Speichern
|
||||
</FluentButton>
|
||||
</FluentDialogFooter>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.FluentUI.AspNetCore.Components;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages.Dialogs;
|
||||
|
||||
public partial class AccountDialog : ComponentBase
|
||||
{
|
||||
private EditContext editContext = null!;
|
||||
|
||||
[CascadingParameter]
|
||||
public FluentDialog Dialog { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public Account Content { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IAccountRepository accountRepository { get; set; } = null!;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
editContext = new EditContext(Content);
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (editContext.Validate())
|
||||
{
|
||||
accountRepository.SaveAccount(Content);
|
||||
await Dialog.CloseAsync(Content);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CancelAsync()
|
||||
{
|
||||
await Dialog.CancelAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
@using Schaad.Accounting.Models
|
||||
@implements IDialogContentComponent<Schaad.Accounting.Models.BookingRule>
|
||||
@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>
|
||||
<EditForm EditContext="@editContext">
|
||||
<DataAnnotationsValidator />
|
||||
<FluentTextField @bind-Value="@Content.LookupText" Label="Suchtext" Style="width: 300px;"/>
|
||||
<FluentNumberField @bind-Value="@Content.LookupValue" Label="Suchbetrag" Style="width: 300px;"/>
|
||||
<FluentTextField @bind-Value="@Content.BookingText" Label="Buchungstext" Style="width: 300px;" />
|
||||
<FluentSelect TOption="Account"
|
||||
Label="Konto"
|
||||
Items="@accounts"
|
||||
Width="300px"
|
||||
Height="250px"
|
||||
Required="true"
|
||||
Placeholder="Konto auswählen..."
|
||||
OptionValue="@(p => p.Id)"
|
||||
OptionText="@(p => p.Number + " / " + p.Name)"
|
||||
@bind-Value="@Content.AccountId" />
|
||||
|
||||
<div>
|
||||
<FluentValidationSummary />
|
||||
</div>
|
||||
</EditForm>
|
||||
</FluentDialogBody>
|
||||
|
||||
<FluentDialogFooter>
|
||||
<FluentButton Appearance="Appearance.Neutral"
|
||||
OnClick="@CancelAsync">
|
||||
Abbrechen
|
||||
</FluentButton>
|
||||
<FluentButton Appearance="Appearance.Accent"
|
||||
OnClick="@SaveAsync">
|
||||
Speichern
|
||||
</FluentButton>
|
||||
</FluentDialogFooter>
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.FluentUI.AspNetCore.Components;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages.Dialogs;
|
||||
|
||||
public partial class BookingRuleDialog : ComponentBase
|
||||
{
|
||||
private EditContext editContext = null!;
|
||||
|
||||
[CascadingParameter]
|
||||
public FluentDialog Dialog { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public BookingRule Content { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IBookingRuleRepository bookingRuleRepository { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IViewService viewService { get; set; } = null!;
|
||||
|
||||
private IReadOnlyList<Account> accounts = [];
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
editContext = new EditContext(Content);
|
||||
accounts = viewService.GetAccountViewList();
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (editContext.Validate())
|
||||
{
|
||||
bookingRuleRepository.SaveBookingRule(Content);
|
||||
await Dialog.CloseAsync(Content);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CancelAsync()
|
||||
{
|
||||
await Dialog.CancelAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
@implements IDialogContentComponent<Schaad.Accounting.Models.BookingText>
|
||||
@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>
|
||||
<EditForm EditContext="@editContext">
|
||||
<DataAnnotationsValidator />
|
||||
<FluentTextField @bind-Value="@Content.Text" Label="Buchungstext" />
|
||||
<div>
|
||||
<FluentValidationSummary />
|
||||
</div>
|
||||
</EditForm>
|
||||
</FluentDialogBody>
|
||||
|
||||
<FluentDialogFooter>
|
||||
<FluentButton Appearance="Appearance.Neutral"
|
||||
OnClick="@CancelAsync">
|
||||
Abbrechen
|
||||
</FluentButton>
|
||||
<FluentButton Appearance="Appearance.Accent"
|
||||
OnClick="@SaveAsync">
|
||||
Speichern
|
||||
</FluentButton>
|
||||
</FluentDialogFooter>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.FluentUI.AspNetCore.Components;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages.Dialogs;
|
||||
|
||||
public partial class BookingTextDialog : ComponentBase
|
||||
{
|
||||
private EditContext editContext = null!;
|
||||
|
||||
[CascadingParameter]
|
||||
public FluentDialog Dialog { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public BookingText Content { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IBookingTextRepository bookingTextRepository { get; set; } = null!;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
editContext = new EditContext(Content);
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (editContext.Validate())
|
||||
{
|
||||
bookingTextRepository.SaveBookingText(Content);
|
||||
await Dialog.CloseAsync(Content);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CancelAsync()
|
||||
{
|
||||
await Dialog.CancelAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
@implements IDialogContentComponent<Schaad.Accounting.Models.SubClass>
|
||||
@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>
|
||||
<EditForm EditContext="@editContext">
|
||||
<DataAnnotationsValidator />
|
||||
<FluentNumberField @bind-Value="@Content.Number" Label="Nummer" Style="width: 300px;"/>
|
||||
<FluentTextField @bind-Value="@Content.Name" Label="Name" Style="width: 300px;"/>
|
||||
<div>
|
||||
<FluentValidationSummary />
|
||||
</div>
|
||||
</EditForm>
|
||||
</FluentDialogBody>
|
||||
|
||||
<FluentDialogFooter>
|
||||
<FluentButton Appearance="Appearance.Neutral"
|
||||
OnClick="@CancelAsync">
|
||||
Abbrechen
|
||||
</FluentButton>
|
||||
<FluentButton Appearance="Appearance.Accent"
|
||||
OnClick="@SaveAsync">
|
||||
Speichern
|
||||
</FluentButton>
|
||||
</FluentDialogFooter>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.FluentUI.AspNetCore.Components;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages.Dialogs;
|
||||
|
||||
public partial class ClassDialog : ComponentBase
|
||||
{
|
||||
private EditContext editContext = null!;
|
||||
|
||||
[CascadingParameter]
|
||||
public FluentDialog Dialog { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public SubClass Content { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ISubclassRepository bookingRuleRepository { get; set; } = null!;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
editContext = new EditContext(Content);
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (editContext.Validate())
|
||||
{
|
||||
bookingRuleRepository.SaveSubClass(Content);
|
||||
await Dialog.CloseAsync(Content);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CancelAsync()
|
||||
{
|
||||
await Dialog.CancelAsync();
|
||||
}
|
||||
}
|
||||
36
Schaad.Accounting.UI/Components/Pages/Error.razor
Normal file
36
Schaad.Accounting.UI/Components/Pages/Error.razor
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
@page "/Error"
|
||||
@using System.Diagnostics
|
||||
|
||||
<PageTitle>Error</PageTitle>
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
||||
|
||||
@code{
|
||||
[CascadingParameter]
|
||||
private HttpContext? HttpContext { get; set; }
|
||||
|
||||
private string? RequestId { get; set; }
|
||||
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
protected override void OnInitialized() =>
|
||||
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
||||
}
|
||||
7
Schaad.Accounting.UI/Components/Pages/Home.razor
Normal file
7
Schaad.Accounting.UI/Components/Pages/Home.razor
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
@page "/"
|
||||
<h3>Home</h3>
|
||||
|
||||
@foreach (var bankTransaction in bankTransactions)
|
||||
{
|
||||
<p>@bankTransaction.BankAccountNumber</p>
|
||||
}
|
||||
19
Schaad.Accounting.UI/Components/Pages/Home.razor.cs
Normal file
19
Schaad.Accounting.UI/Components/Pages/Home.razor.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages;
|
||||
|
||||
public partial class Home : ComponentBase
|
||||
{
|
||||
[Inject]
|
||||
private IViewService viewService { get; set; } = null!;
|
||||
|
||||
private List<BankTransaction> bankTransactions = null!;
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
bankTransactions = viewService.GetOpenBankTransactionList();
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
@page "/BalanceReport"
|
||||
|
||||
<h3>@header</h3>
|
||||
<a href="javascript:window.print()" class="hidePrint">Print</a>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<table class="report">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Konto</th>
|
||||
<th>Bezeichnung</th>
|
||||
<th class="right">Aktiven</th>
|
||||
<th class="right">Passiven</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="4"><hr /></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="4" class="header">Aktiven</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4"><hr /></td>
|
||||
</tr>
|
||||
@foreach (var account in balance.ActivaAccountList.OrderBy(m => m.Number))
|
||||
{
|
||||
@if (account.BalanceCHF == 0) { continue; }
|
||||
|
||||
<tr>
|
||||
<td>@account.Number</td>
|
||||
<td>@account.Name</td>
|
||||
<td class="right">@Math.Abs(account.BalanceCHF).ToFormattedString()</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td colspan="4"><hr /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="total">Total Aktiven</td>
|
||||
<td class="right total">CHF @balance.TotalActivaCHF.ToFormattedString()</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4"><hr /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4" class="header">Passiven</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4"><hr /></td>
|
||||
</tr>
|
||||
@foreach (var account in balance.PassivaAccountList.OrderBy(m => m.Number))
|
||||
{
|
||||
@if (account.BalanceCHF == 0) { continue; }
|
||||
|
||||
<tr>
|
||||
<td>@account.Number</td>
|
||||
<td>@account.Name</td>
|
||||
<td></td>
|
||||
<td class="right">@Math.Abs(account.BalanceCHF).ToFormattedString()</td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td colspan="4"><hr /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="total">Total Passiven</td>
|
||||
<td></td>
|
||||
<td class="right total">@balance.TotalPassivaCHF.ToFormattedString()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4"><hr /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="total">Total</td>
|
||||
<td class="right">@balance.TotalActivaCHF.ToFormattedString()</td>
|
||||
<td class="right">@balance.TotalPassivaCHF.ToFormattedString()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="total">Eigenkapital</td>
|
||||
<td class="right sum"></td>
|
||||
<td class="right sum">@win.ToFormattedString()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="total">Bilanzsumme</td>
|
||||
<td class="right total sum">CHF @balance.TotalActivaCHF.ToFormattedString()</td>
|
||||
<td class="right total sum">CHF @((balance.TotalPassivaCHF + win).ToFormattedString())</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4" style="height: 30px"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4" class="footer">@footer</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Schaad.Accounting.Datasets.Reports;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages.Reports;
|
||||
|
||||
public partial class BalanceReport : ComponentBase
|
||||
{
|
||||
[Inject]
|
||||
private IViewService viewService { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ISettingsService settingsService { get; set; } = null!;
|
||||
|
||||
private BalanceDataset balance = null!;
|
||||
private decimal win;
|
||||
private string header = null!;
|
||||
private string footer = null!;
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
balance = viewService.GetBalanceView();
|
||||
win = balance.TotalActivaCHF - balance.TotalPassivaCHF;
|
||||
(header, footer) = Report.GetViewDataTitleAndFooter("Bilanz", settingsService);
|
||||
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
@page "/BalanceSheetReport"
|
||||
|
||||
<h3>@header</h3>
|
||||
<a href="javascript:window.print()" class="hidePrint">Print</a>
|
||||
<br />
|
||||
<br />
|
||||
<table class="report">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="3" class="header">Einnahmen</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"><hr /></td>
|
||||
</tr>
|
||||
@foreach (var account in balanceSheet.ActivaAccountList.OrderBy(m => m.Number))
|
||||
{
|
||||
@if (account.StartBalanceCHF == 0) { continue; }
|
||||
|
||||
<tr>
|
||||
<td>@account.Number</td>
|
||||
<td>Vortrag von @(balanceSheet.Year - 1) / @account.Name</td>
|
||||
<td class="right">@Math.Abs(account.StartBalanceCHF).ToFormattedString()</td>
|
||||
</tr>
|
||||
}
|
||||
@foreach (var account in balanceSheet.IncomeAccountList.OrderBy(m => m.Number))
|
||||
{
|
||||
@if (account.BalanceCHF == 0) { continue; }
|
||||
|
||||
<tr>
|
||||
<td>@account.Number</td>
|
||||
<td>@account.Name</td>
|
||||
<td class="right">@Math.Abs(account.BalanceCHF).ToFormattedString()</td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td colspan="3"><hr /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3" class="header">Ausgaben</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"><hr /></td>
|
||||
</tr>
|
||||
@foreach (var account in balanceSheet.ExpensesAccountList.OrderBy(m => m.Number))
|
||||
{
|
||||
@if (account.BalanceCHF == 0) { continue; }
|
||||
|
||||
<tr>
|
||||
<td>@account.Number</td>
|
||||
<td>@account.Name</td>
|
||||
<td class="right">@account.BalanceCHF.ToFormattedString()</td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td colspan="3"><hr /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">Total Einnahmen</td>
|
||||
<td class="right">@balanceSheet.ProfitCHF.ToFormattedString()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">Total Ausgaben</td>
|
||||
<td class="right sum">-@balanceSheet.LossCHF.ToFormattedString()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="total">Übertrag @(balanceSheet.Year + 1)</td>
|
||||
<td class="right total sum">CHF @win.ToFormattedString()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4" style="height: 30px"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4" class="footer">@footer</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Schaad.Accounting.Datasets.Reports;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages.Reports;
|
||||
|
||||
public partial class BalanceSheetReport : ComponentBase
|
||||
{
|
||||
[Inject]
|
||||
private IViewService viewService { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ISettingsService settingsService { get; set; } = null!;
|
||||
|
||||
private BalanceSheetDataset balanceSheet = null!;
|
||||
private decimal win;
|
||||
private string header = null!;
|
||||
private string footer = null!;
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
balanceSheet = viewService.GetBalanceSheetView(settingsService.GetYear());
|
||||
win = balanceSheet.ProfitCHF - balanceSheet.LossCHF;
|
||||
(header, footer) = Report.GetViewDataTitleAndFooter("Jahresabschluss", settingsService);
|
||||
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
}
|
||||
112
Schaad.Accounting.UI/Components/Pages/Reports/DetailReport.razor
Normal file
112
Schaad.Accounting.UI/Components/Pages/Reports/DetailReport.razor
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
@page "/DetailReport"
|
||||
|
||||
<h3>@header</h3>
|
||||
<a href="javascript:window.print()" class="hidePrint">Print</a>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<table class="report">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Konto</th>
|
||||
<th>Bezeichnung</th>
|
||||
<th class="right">Betrag</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="3"><hr /></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="3" class="header">Einkünfte</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"><hr /></td>
|
||||
</tr>
|
||||
@foreach(var account in accounts.Where(m => m.Class == 3).OrderBy(m => m.Number))
|
||||
{
|
||||
@if (account.Balance == 0) {continue;}
|
||||
|
||||
<tr>
|
||||
<td class="header">@account.Number</td>
|
||||
<td class="header">@account.Name</td>
|
||||
<td class="header right">@Math.Abs(account.Balance).ToFormattedString()</td>
|
||||
</tr>
|
||||
|
||||
@foreach(var trx in transactions.Where(t => t.TargetAccountId == account.Id || t.OriginAccountId == account.Id).OrderBy(t => t.ValueDate))
|
||||
{
|
||||
<tr>
|
||||
<td>@trx.ValueDate.ToString("dd.MM.yyyy")</td>
|
||||
<td>@trx.Text</td>
|
||||
<td class="right">
|
||||
@if (trx.OriginAccountId == account.Id)
|
||||
{
|
||||
@trx.Value.ToFormattedString();
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>-</span>@trx.Value.ToFormattedString();
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td colspan="3"><hr /></td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td colspan="2" class="total right">Total Einkünfte</td>
|
||||
<td class="right total">@profit.ToFormattedString()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"><hr /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3" class="header">Ausgaben</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"><hr /></td>
|
||||
</tr>
|
||||
@foreach(var account in accounts.Where(m => m.Class == 4).OrderBy(m => m.Number))
|
||||
{
|
||||
@if (account.Balance == 0) {continue;}
|
||||
|
||||
<tr>
|
||||
<td class="header">@account.Number</td>
|
||||
<td class="header">@account.Name</td>
|
||||
<td class="header right">@account.Balance.ToFormattedString()</td>
|
||||
</tr>
|
||||
|
||||
@foreach(var trx in transactions.Where(t => t.TargetAccountId == account.Id || t.OriginAccountId == account.Id).OrderBy(t => t.ValueDate))
|
||||
{
|
||||
<tr>
|
||||
<td>@trx.ValueDate.ToString("dd.MM.yyyy")</td>
|
||||
<td>@trx.Text</td>
|
||||
<td class="right">
|
||||
@if (trx.TargetAccountId == account.Id)
|
||||
{
|
||||
@trx.Value.ToFormattedString();
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>-</span>@trx.Value.ToFormattedString();
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td colspan="3"><hr /></td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td colspan="2" class="total right">Total Ausgaben</td>
|
||||
<td class="right total">@loss.ToFormattedString()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4" style="height: 30px"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4" class="footer">@footer</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Schaad.Accounting.Datasets;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages.Reports;
|
||||
|
||||
public partial class DetailReport : ComponentBase
|
||||
{
|
||||
[Inject]
|
||||
private IViewService viewService { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ISettingsService settingsService { get; set; } = null!;
|
||||
|
||||
private IReadOnlyList<AccountDataset> accounts = null!;
|
||||
private IReadOnlyList<TransactionDataset> transactions = null!;
|
||||
private decimal profit;
|
||||
private decimal loss;
|
||||
private string header = null!;
|
||||
private string footer = null!;
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
accounts = viewService.GetAccountViewList();
|
||||
transactions = viewService.GetTransactionViewList();
|
||||
|
||||
profit = Math.Abs(accounts.Where(m => m.Class == 3).Sum(m => m.Balance));
|
||||
loss = Math.Abs(accounts.Where(m => m.Class == 4).Sum(m => m.Balance));
|
||||
|
||||
(header, footer) = Report.GetViewDataTitleAndFooter("Detailaufstellung", settingsService);
|
||||
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
@page "/ProfitLossReport"
|
||||
|
||||
<h3>@header</h3>
|
||||
<a href="javascript:window.print()" class="hidePrint">Print</a>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<table class="report">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Konto</th>
|
||||
<th>Bezeichnung</th>
|
||||
<th class="right">Aufwand</th>
|
||||
<th class="right">Ertrag</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="4"><hr /></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="4" class="header">Einkünfte</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4"><hr /></td>
|
||||
</tr>
|
||||
@foreach(var account in accounts.Where(m => m.Class == 3).OrderBy(m => m.Number))
|
||||
{
|
||||
@if (account.Balance == 0) {continue;}
|
||||
|
||||
<tr>
|
||||
<td>@account.Number</td>
|
||||
<td>@account.Name</td>
|
||||
<td></td>
|
||||
<td class="right">@Math.Abs(account.Balance).ToFormattedString()</td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td colspan="4"><hr /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="total">Total Einkünfte</td>
|
||||
<td></td>
|
||||
<td class="right total">@profit.ToFormattedString()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4"><hr /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4" class="header">Ausgaben</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4"><hr /></td>
|
||||
</tr>
|
||||
@foreach(var account in accounts.Where(m => m.Class == 4).OrderBy(m => m.Number))
|
||||
{
|
||||
@if (account.Balance == 0) {continue;}
|
||||
|
||||
<tr>
|
||||
<td>@account.Number</td>
|
||||
<td>@account.Name</td>
|
||||
<td class="right">@Math.Abs(account.Balance).ToFormattedString()</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td colspan="4"><hr /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="total">Total Ausgaben</td>
|
||||
<td class="right total">@loss.ToFormattedString()</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4"><hr /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="total"></td>
|
||||
<td class="right total">@loss.ToFormattedString()</td>
|
||||
<td class="right total">@profit.ToFormattedString()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="total">Gewinn/Verlust</td>
|
||||
<td class="right total sum">@win.ToFormattedString()</td>
|
||||
<td class="right total sum"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="total">Total</td>
|
||||
<td class="right total sum">@profit.ToFormattedString()</td>
|
||||
<td class="right total sum">@profit.ToFormattedString()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4" style="height: 30px"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4" class="footer">@footer</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Schaad.Accounting.Datasets;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages.Reports;
|
||||
|
||||
public partial class ProfitLossReport : ComponentBase
|
||||
{
|
||||
[Inject]
|
||||
private IViewService viewService { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ISettingsService settingsService { get; set; } = null!;
|
||||
|
||||
private IReadOnlyList<AccountDataset> accounts = null!;
|
||||
private decimal profit;
|
||||
private decimal loss;
|
||||
private decimal win;
|
||||
private string header = null!;
|
||||
private string footer = null!;
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
accounts = viewService.GetAccountViewList();
|
||||
profit = Math.Abs(accounts.Where(m => m.Class == 3).Sum(m => m.Balance));
|
||||
loss = Math.Abs(accounts.Where(m => m.Class == 4).Sum(m => m.Balance));
|
||||
win = profit-loss;
|
||||
|
||||
(header, footer) = Report.GetViewDataTitleAndFooter("Erfolgsrechnung", settingsService);
|
||||
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
}
|
||||
23
Schaad.Accounting.UI/Components/Pages/Reports/Report.cs
Normal file
23
Schaad.Accounting.UI/Components/Pages/Reports/Report.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using Schaad.Accounting.Interfaces;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages.Reports;
|
||||
|
||||
public static class Report
|
||||
{
|
||||
public static (string header, string footer) GetViewDataTitleAndFooter(string title, ISettingsService settingsService)
|
||||
{
|
||||
var header = $"{title} {settingsService.GetMandator()} {settingsService.GetYear()}";
|
||||
|
||||
var footer = "Stand: ";
|
||||
if (DateTime.Now.Year == settingsService.GetYear())
|
||||
{
|
||||
footer += DateTime.Now.ToString("dd.MM.yyyy");
|
||||
}
|
||||
else
|
||||
{
|
||||
footer += "31.12." + settingsService.GetYear();
|
||||
}
|
||||
|
||||
return (header, footer);
|
||||
}
|
||||
}
|
||||
53
Schaad.Accounting.UI/Components/Pages/Transactions.razor
Normal file
53
Schaad.Accounting.UI/Components/Pages/Transactions.razor
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
@page "/transactions"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Transaktionen</PageTitle>
|
||||
|
||||
<FluentStack Orientation="Orientation.Horizontal" Width="100%">
|
||||
<FluentNavMenu Width="700">
|
||||
@foreach (var gr in accounts.GroupBy(a => a.ClassName))
|
||||
{
|
||||
<FluentNavGroup Title="@(gr.Key)" Expanded="true">
|
||||
@foreach (var account in gr)
|
||||
{
|
||||
<FluentNavLink OnClick="() => ShowTransactions(account.Id)" Icon="@(new Icons.Regular.Size24.CalendarAgenda())">
|
||||
<FluentStack Orientation="Orientation.Horizontal">
|
||||
<div>
|
||||
@account.Number / @account.Name
|
||||
</div>
|
||||
<div style="margin-left: auto;">
|
||||
<FluentBadge Appearance="Appearance.Neutral">
|
||||
@account.BalanceCHF.ToString("C2")
|
||||
</FluentBadge>
|
||||
</div>
|
||||
</FluentStack>
|
||||
|
||||
</FluentNavLink>
|
||||
}
|
||||
</FluentNavGroup>
|
||||
}
|
||||
</FluentNavMenu>
|
||||
|
||||
<FluentBodyContent>
|
||||
<FluentPaginator State="@pagination" SummaryTemplate="@template" />
|
||||
|
||||
<FluentDataGrid Items="@transactionList" Pagination="@pagination">
|
||||
<PropertyColumn Property="@(p => p.BookingDate)" Format="dd.MM.yyyy" Sortable="true" IsDefaultSortColumn="true" InitialSortDirection="SortDirection.Descending" />
|
||||
<PropertyColumn Property="@(p => p.ValueDate)" Format="dd.MM.yyyy" Sortable="true" />
|
||||
<PropertyColumn Property="@(p => p.Text)" />
|
||||
<TemplateColumn Title="Gegenkonto">
|
||||
@GetAccountName(context)
|
||||
</TemplateColumn>
|
||||
<PropertyColumn Property="@(p => p.Value)" Format="C2" Align="Align.End"/>
|
||||
|
||||
|
||||
</FluentDataGrid>
|
||||
</FluentBodyContent>
|
||||
|
||||
</FluentStack>
|
||||
|
||||
@code
|
||||
{
|
||||
private RenderFragment template = @<span />;
|
||||
PaginationState pagination = new PaginationState() { ItemsPerPage = 20 };
|
||||
}
|
||||
38
Schaad.Accounting.UI/Components/Pages/Transactions.razor.cs
Normal file
38
Schaad.Accounting.UI/Components/Pages/Transactions.razor.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Schaad.Accounting.Datasets;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
|
||||
namespace Schaad.Accounting.UI.Components.Pages;
|
||||
|
||||
public partial class Transactions : ComponentBase
|
||||
{
|
||||
[Inject]
|
||||
private IViewService viewService { get; set; } = null!;
|
||||
|
||||
private IReadOnlyList<AccountDataset> accounts = null!;
|
||||
|
||||
private IQueryable<TransactionDataset>? transactionList;
|
||||
private string? selectedAccountId;
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
accounts = viewService.GetAccountViewList();
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private void ShowTransactions(string accountId)
|
||||
{
|
||||
selectedAccountId = accountId;
|
||||
transactionList = viewService.GetTransactionViewList(accountId).AsQueryable();
|
||||
}
|
||||
|
||||
private string GetAccountName(TransactionDataset transaction)
|
||||
{
|
||||
if (selectedAccountId == transaction.OriginAccountId)
|
||||
{
|
||||
return transaction.TargetAccount.Name;
|
||||
}
|
||||
|
||||
return transaction.OriginAccount.Name;
|
||||
}
|
||||
}
|
||||
6
Schaad.Accounting.UI/Components/Routes.razor
Normal file
6
Schaad.Accounting.UI/Components/Routes.razor
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<Router AppAssembly="typeof(Program).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
</Found>
|
||||
</Router>
|
||||
16
Schaad.Accounting.UI/Components/_Imports.razor
Normal file
16
Schaad.Accounting.UI/Components/_Imports.razor
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
@using System.ComponentModel.DataAnnotations
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.FluentUI.AspNetCore.Components
|
||||
@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons
|
||||
@using Microsoft.JSInterop
|
||||
@using Plotly.Blazor
|
||||
@using Schaad.Accounting.UI
|
||||
@using Schaad.Accounting.UI.Components
|
||||
@using Schaad.Accounting.Interfaces
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue