diff --git a/Financier.Desktop.gif b/Financier.Desktop.gif index 9469ac62..00b9d343 100644 Binary files a/Financier.Desktop.gif and b/Financier.Desktop.gif differ diff --git a/README.md b/README.md index 7c4b0767..dac03608 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,34 @@ -| What | Badge| -| ---- | ---- | -| Build | [![Build Status](https://dev.azure.com/khmelovskyi/Financier.Desktop/_apis/build/status/vov4uk.Financier.Desktop?branchName=master)](https://dev.azure.com/khmelovskyi/Financier.Desktop/_build/latest?definitionId=2&branchName=master)| -| Latest | [![GitHub (pre-)release](https://img.shields.io/github/v/release/vov4uk/financier.desktop?include_prereleases)](https://github.com/vov4uk/Financier.Desktop/releases)| -| Total| [![Github Releases](https://img.shields.io/github/downloads/vov4uk/Financier.Desktop/total)](https://github.com/vov4uk/Financier.Desktop/releases)| -| Sonar | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vov4uk_Financier.Desktop&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vov4uk_Financier.Desktop)| - -# Financier Desktop -[**Install the .NET Desktop Runtime.**](https://dotnet.microsoft.com/en-us/download/dotnet/6.0/runtime) - -MVP (minimum viable product) application, with enough functions to work with Financier .backup on Windows. -More info on [Github pages](https://vov4uk.github.io/Financier.Desktop/) +

Financier Desktop

+

+ +Build + + +GitHub release + + +GitHub Latest + + +Github Releases + + +Quality Gate Status + + +

+ +[**Install the .NET Desktop Runtime.**](https://dotnet.microsoft.com/en-us/download/dotnet/8.0/runtime) + +MVP (minimum viable product) application, with enough functions to work with Financisto .backup on Windows. +More info on [Github pages](https://vov4uk.github.io/Financier.Desktop) ## About Desktop version of [Financier](https://github.com/handydevcom/financier "Financier") which is a fork of the great [Financisto](https://github.com/dsolonenko/financisto) app. Financisto is an open-source personal finance tracker for Android platform. ## Supported features -- Open/save Financier (Financisto) .backup files +- Open/save Financisto .backup files - View transactions list - Transactions search and filtering - Edit transactions/transfers @@ -24,6 +36,7 @@ More info on [Github pages](https://vov4uk.github.io/Financier.Desktop/) - Edit entities (projects, locations, payees...) - Import CSV report from Monobank - Import PDF report from A-Bank +- Import PDF report from PUMB - Parse recipiet text to SPLIT transaction - Save .backup as SQLite database (.db file) - Reporting (kudos to [@2ruslan](https://github.com/2ruslan)) diff --git a/src/Financier.Adapter/BackupReader.cs b/src/Financier.Adapter/BackupReader.cs index 76a76f96..7ec41b45 100644 --- a/src/Financier.Adapter/BackupReader.cs +++ b/src/Financier.Adapter/BackupReader.cs @@ -39,7 +39,7 @@ private void ReadHeader() break; case Backup.VERSION_NAME: - BackupVersion.Version = Version.Parse(line.Value); + BackupVersion.Version = line.Value; break; case Backup.DATABASE_VERSION: diff --git a/src/Financier.Adapter/BackupVersion.cs b/src/Financier.Adapter/BackupVersion.cs index c3e0d606..56254c56 100644 --- a/src/Financier.Adapter/BackupVersion.cs +++ b/src/Financier.Adapter/BackupVersion.cs @@ -6,7 +6,7 @@ public class BackupVersion { public string Package { get; set; } public int VersionCode { get; set; } - public Version Version { get; set; } + public string Version { get; set; } public int DatabaseVersion { get; set; } } } diff --git a/src/Financier.Common/Assets/Generic.xaml b/src/Financier.Common/Assets/Generic.xaml index 12b40fdd..fa59169f 100644 --- a/src/Financier.Common/Assets/Generic.xaml +++ b/src/Financier.Common/Assets/Generic.xaml @@ -34,13 +34,13 @@ diff --git a/src/Financier.Common/Filters/PeriodFilter.xaml.cs b/src/Financier.Common/Filters/PeriodFilter.xaml.cs index 09a5a86a..24c36dcd 100644 --- a/src/Financier.Common/Filters/PeriodFilter.xaml.cs +++ b/src/Financier.Common/Filters/PeriodFilter.xaml.cs @@ -88,8 +88,8 @@ private static (DateTime? from, DateTime? to) UpdatePeriod(PeriodType type, Date { var today = DateTime.Today; - from = new DateTime(today.AddMonths(-1).Year, today.AddMonths(-1).Month, 1); - to = new DateTime(today.Year, today.Month, 1).AddMilliseconds(-1); + from = new DateTime(today.AddMonths(-1).Year, today.AddMonths(-1).Month, 1,0,0,0, DateTimeKind.Local); + to = new DateTime(today.Year, today.Month, 1,0,0,0, DateTimeKind.Local).AddMilliseconds(-1); } break; case PeriodType.CurrentWeek: @@ -108,8 +108,8 @@ private static (DateTime? from, DateTime? to) UpdatePeriod(PeriodType type, Date { var today = DateTime.Today; - from = new DateTime(today.Year, today.Month, 1); - to = new DateTime(today.AddMonths(1).Year, today.AddMonths(1).Month, 1).AddMilliseconds(-1); + from = new DateTime(today.Year, today.Month, 1, 0, 0, 0, DateTimeKind.Local); + to = new DateTime(today.AddMonths(1).Year, today.AddMonths(1).Month, 1, 0, 0, 0, DateTimeKind.Local).AddMilliseconds(-1); } break; case PeriodType.PreviousAndCurrentWeek: @@ -128,8 +128,8 @@ private static (DateTime? from, DateTime? to) UpdatePeriod(PeriodType type, Date { var today = DateTime.Today; - from = new DateTime(today.AddMonths(-1).Year, today.AddMonths(-1).Month, 1); - to = new DateTime(today.AddMonths(1).Year, today.AddMonths(1).Month, 1).AddMilliseconds(-1); + from = new DateTime(today.AddMonths(-1).Year, today.AddMonths(-1).Month, 1, 0, 0, 0, DateTimeKind.Local); + to = new DateTime(today.AddMonths(1).Year, today.AddMonths(1).Month, 1, 0, 0, 0, DateTimeKind.Local).AddMilliseconds(-1); } break; default: diff --git a/src/Financier.Common/Financier.Common.csproj b/src/Financier.Common/Financier.Common.csproj index a3cf18cd..8c58776e 100644 --- a/src/Financier.Common/Financier.Common.csproj +++ b/src/Financier.Common/Financier.Common.csproj @@ -1,7 +1,7 @@  - net6.0-windows + net8.0-windows7.0 enable true diff --git a/src/Financier.DataAccess/Abstractions/IBaseRepository.cs b/src/Financier.DataAccess/Abstractions/IBaseRepository.cs index 3717c54c..9ab1f859 100644 --- a/src/Financier.DataAccess/Abstractions/IBaseRepository.cs +++ b/src/Financier.DataAccess/Abstractions/IBaseRepository.cs @@ -24,7 +24,7 @@ public interface IBaseRepository where T : class Task> FindManyAsync(Expression> predicate, params Expression>[] includes); - Task> FindManyAsync( + Task> FindManyAndProjectAsync( Expression> predicate, Expression> projection, params Expression>[] includes); diff --git a/src/Financier.DataAccess/BaseRepository.cs b/src/Financier.DataAccess/BaseRepository.cs index f021da28..fb4825c5 100644 --- a/src/Financier.DataAccess/BaseRepository.cs +++ b/src/Financier.DataAccess/BaseRepository.cs @@ -78,7 +78,7 @@ public virtual async Task> FindManyAsync(Expression> predi return await result?.ToListAsync(); } - public virtual async Task> FindManyAsync( + public virtual async Task> FindManyAndProjectAsync( Expression> predicate, Expression> projection, params Expression>[] includes) diff --git a/src/Financier.DataAccess/FinancierDatabase.cs b/src/Financier.DataAccess/FinancierDatabase.cs index 07018a2c..ccbed5d9 100644 --- a/src/Financier.DataAccess/FinancierDatabase.cs +++ b/src/Financier.DataAccess/FinancierDatabase.cs @@ -147,11 +147,14 @@ public async Task RebuildAccountBalanceAsync(int accountId) } var acc = context.Accounts.FirstOrDefault(x => x.Id == accountId); - acc.TotalAmount = balance; var lastTransaction = transactions.LastOrDefault(); - acc.LastTransactionDate = lastTransaction?.DateTime ?? 0; - acc.LastTransactionId = lastTransaction?.Id ?? 0; - context.Accounts.Update(acc); + if (acc != null) + { + acc.TotalAmount = balance; + acc.LastTransactionDate = lastTransaction?.DateTime ?? 0; + acc.LastTransactionId = lastTransaction?.Id ?? 0; + context.Accounts.Update(acc); + } await context.SaveChangesAsync(); } } @@ -197,9 +200,17 @@ public async Task> GetSubTransactionsAsync(int id) if (id != 0) { using var uow = CreateUnitOfWork(); - return (await uow.GetRepository().FindManyAsync( - predicate: x => x.ParentId == id, - includes: new Expression>[]{ o => o.OriginalCurrency, c => c.Category})) ?? Array.Empty().ToList(); + var subTransactions = await uow.GetRepository(). + FindManyAsync( + x => x.ParentId == id, + o => o.OriginalCurrency, + c => c.Category); + + if (subTransactions == null) + { + return Array.Empty(); + } + return subTransactions; } return Array.Empty(); @@ -250,7 +261,7 @@ public async Task InsertOrUpdateAsync(IEnumerable entities) int ordinal = reader.GetOrdinal(customAttribute.Name); object obj = ordinal != -1 ? reader.GetValue(ordinal) : - throw new Exception(string.Format("Class [{0}] have attribute of field [{1}] which not exist in reader", this.GetType(), customAttribute.Name)); + throw new InvalidCastException(string.Format("Class [{0}] have attribute of field [{1}] which not exist in reader", this.GetType(), customAttribute.Name)); if (obj != DBNull.Value) { diff --git a/src/Financier.Desktop/Data/BaseTransactionDTO.cs b/src/Financier.Desktop/Data/BaseTransactionDTO.cs index ca64aa01..d702a5cf 100644 --- a/src/Financier.Desktop/Data/BaseTransactionDTO.cs +++ b/src/Financier.Desktop/Data/BaseTransactionDTO.cs @@ -37,7 +37,7 @@ public DateTime Time public DateTime DateTime { - get { return new DateTime(date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second); } + get { return new DateTime(date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second, DateTimeKind.Local); } } public int Id diff --git a/src/Financier.Desktop/Data/TransactionDTO.cs b/src/Financier.Desktop/Data/TransactionDTO.cs index 06b9f765..2ea7ce4a 100644 --- a/src/Financier.Desktop/Data/TransactionDTO.cs +++ b/src/Financier.Desktop/Data/TransactionDTO.cs @@ -56,7 +56,14 @@ public TransactionDto(Transaction transaction, IEnumerable subTrans } else { - list.Add(new TransactionDto(t)); + var tr = new TransactionDto(t); + + // if transaction not in home currency, replace FromAmount with OriginalFromAmount to show correct values + if (IsOriginalFromAmountVisible) + { + tr.FromAmount = tr.OriginalFromAmount ?? 0; + } + list.Add(tr); } } @@ -83,7 +90,7 @@ public TransactionDto(Transaction transaction) public AccountFilterModel FromAccount { - get => fromAccount ??= DbManual.Account?.FirstOrDefault(x => x.Id == FromAccountId); + get => fromAccount ??= DbManual.Account?.Find(x => x.Id == FromAccountId); set { if (SetProperty(ref fromAccount, value)) @@ -110,12 +117,12 @@ public int FromAccountId public CurrencyModel FromAccountCurrency { - get => DbManual.Currencies?.FirstOrDefault(x => x.Id == (FromAccount != null ? FromAccount.CurrencyId : 0)); + get => DbManual.Currencies?.Find(x => x.Id == (FromAccount != null ? FromAccount.CurrencyId : 0)); } public CategoryModel Category { - get => category ??= DbManual.Category?.FirstOrDefault(x => x.Id == CategoryId); + get => category ??= DbManual.Category?.Find(x => x.Id == CategoryId); set { if (SetProperty(ref category, value)) @@ -187,7 +194,7 @@ public int? LocationId public CurrencyModel OriginalCurrency { - get => currency ??= DbManual.Currencies?.FirstOrDefault(x => x.Id == OriginalCurrencyId); + get => currency ??= DbManual.Currencies?.Find(x => x.Id == OriginalCurrencyId); set { if (SetProperty(ref currency, value)) @@ -268,7 +275,7 @@ public string RateString if (Rate != 0) { var d = 1.0 / Rate; - var localCurrency = DbManual.Currencies?.FirstOrDefault(x => x.Id == fromAccount?.Id); + var localCurrency = DbManual.Currencies?.Find(x => x.Id == fromAccount?.Id); return $"1{currency?.Name}={Rate:F5}{localCurrency?.Name}, 1{localCurrency?.Name}={d:F5}{currency?.Name}"; } @@ -322,7 +329,9 @@ public void RecalculateUnSplitAmount() internal void RecalculateRate() { if (originalFromAmount != null && originalFromAmount != 0) + { Rate = Math.Abs(fromAmount / 100.0 / (originalFromAmount.Value / 100.0)); + } } } } \ No newline at end of file diff --git a/src/Financier.Desktop/Data/TransferDTO.cs b/src/Financier.Desktop/Data/TransferDTO.cs index 1cfa54d6..33335717 100644 --- a/src/Financier.Desktop/Data/TransferDTO.cs +++ b/src/Financier.Desktop/Data/TransferDTO.cs @@ -29,8 +29,8 @@ public TransferDto(Transaction transaction) toAmount = transaction.ToAmount; date = UnixTimeConverter.Convert(transaction.DateTime).Date; time = UnixTimeConverter.Convert(transaction.DateTime); - fromAccount = DbManual.Account.FirstOrDefault(x => x.Id == fromAccountId); - toAccount = DbManual.Account.FirstOrDefault(x => x.Id == toAccountId); + fromAccount = DbManual.Account.Find(x => x.Id == fromAccountId); + toAccount = DbManual.Account.Find(x => x.Id == toAccountId); } public AccountFilterModel FromAccount @@ -50,7 +50,7 @@ public AccountFilterModel FromAccount public CurrencyModel FromAccountCurrency { - get => DbManual.Currencies?.FirstOrDefault(x => x.Id == (FromAccount != null ? FromAccount.CurrencyId : 0)); + get => DbManual.Currencies?.Find(x => x.Id == (FromAccount != null ? FromAccount.CurrencyId : 0)); } public int FromAccountId @@ -126,7 +126,7 @@ public int ToAccountId public CurrencyModel ToAccountCurrency { - get => DbManual.Currencies?.FirstOrDefault(x => x.Id == (ToAccount != null ? ToAccount.CurrencyId : 0)); + get => DbManual.Currencies?.Find(x => x.Id == (ToAccount != null ? ToAccount.CurrencyId : 0)); } public long ToAmount diff --git a/src/Financier.Desktop/Financier.Desktop.csproj b/src/Financier.Desktop/Financier.Desktop.csproj index 7a522573..590dff7d 100644 --- a/src/Financier.Desktop/Financier.Desktop.csproj +++ b/src/Financier.Desktop/Financier.Desktop.csproj @@ -2,7 +2,7 @@ WinExe - net6.0-windows + net8.0-windows7.0 true true Images\ic_launcher.ico @@ -14,10 +14,7 @@ - - - - + PreserveNewest @@ -92,16 +89,16 @@ - - - + + + - + - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Financier.Desktop/Images/folder.png b/src/Financier.Desktop/Images/folder.png new file mode 100644 index 00000000..4ffd333c Binary files /dev/null and b/src/Financier.Desktop/Images/folder.png differ diff --git a/src/Financier.Desktop/MainWindow.xaml b/src/Financier.Desktop/MainWindow.xaml index d074fce5..bbcbdb44 100644 --- a/src/Financier.Desktop/MainWindow.xaml +++ b/src/Financier.Desktop/MainWindow.xaml @@ -52,24 +52,56 @@ + + + + + + + + + + + - - + - - + + - + + + + + + + + + + + + + + + + + + + + - + - + + DataContext="{Binding}" + Visibility="{Binding Path=IsTransactionPageSelected, Converter={StaticResource BoolToVis}, Mode=OneWay}" + Header="Transaction"> @@ -198,9 +265,9 @@ + Selector.IsSelected="{Binding Path=IsProjectPageSelected, Mode=OneWay}" + Visibility="{Binding Path=IsProjectPageSelected, Converter={StaticResource BoolToVis}, Mode=OneWay}" + DataContext="{Binding}"> @@ -254,7 +321,7 @@ - + \ No newline at end of file diff --git a/src/Financier.Desktop/MainWindow.xaml.cs b/src/Financier.Desktop/MainWindow.xaml.cs index 72d61ddd..ed7e3076 100644 --- a/src/Financier.Desktop/MainWindow.xaml.cs +++ b/src/Financier.Desktop/MainWindow.xaml.cs @@ -12,6 +12,8 @@ using Financier.Common.Model; using System.Reflection; using System.Threading.Tasks; +using Microsoft.Win32; +using Financier.Desktop.Properties; namespace Financier.Desktop { @@ -45,7 +47,8 @@ public MainWindow() private void RibbonWindow_Loaded(object sender, RoutedEventArgs e) { - var bakupFolder = @$"C:\Users\{Environment.UserName}\Dropbox\apps\FinancierAndroid"; + var bakupFolder = Settings.Default.DefaultBackupDir ?? @$"C:\Users\{Environment.UserName}\Dropbox\apps\Financisto Holo"; + ViewModel.DefaultBackupDirectory = Settings.Default.DefaultBackupDir; if (Directory.Exists(bakupFolder)) { var backupFile = Directory.EnumerateFiles(bakupFolder, BackupFormat).OrderByDescending(x => x).FirstOrDefault(); @@ -74,5 +77,21 @@ private void Exit_Click(object sender, RoutedEventArgs e) { Application.Current.Shutdown(); } + + private void RibbonApplicationMenuItem_Click(object sender, RoutedEventArgs e) + { + Microsoft.Win32.OpenFolderDialog openFileDialog = new OpenFolderDialog + { + Multiselect = false + }; + + if (openFileDialog.ShowDialog() == true) + { + var currentFolder = openFileDialog.FolderName; + Settings.Default.DefaultBackupDir = currentFolder; + Settings.Default.Save(); + ViewModel.DefaultBackupDirectory = currentFolder; + } + } } } diff --git a/src/Financier.Desktop/MainWindowVM.cs b/src/Financier.Desktop/MainWindowVM.cs index 720f58a1..087bd879 100644 --- a/src/Financier.Desktop/MainWindowVM.cs +++ b/src/Financier.Desktop/MainWindowVM.cs @@ -49,6 +49,7 @@ public class MainWindowVM : BindableBase private readonly IEntityReader entityReader; private LocationsVM locationsVm; private string openBackupPath; + private string defaultBackupDirectory; private bool isLoading; private PayeesVM payeesVm; private ProjectsVM projectsVm; @@ -103,6 +104,11 @@ public string OpenBackupPath get => openBackupPath; private set => SetProperty(ref openBackupPath, value); } + public string DefaultBackupDirectory + { + get => defaultBackupDirectory; + internal set => SetProperty(ref defaultBackupDirectory, value); + } public LocationsVM Locations { @@ -250,7 +256,7 @@ private BindableBase GetOrCreatePage(Type type) return _pages[type]; } - default: throw new NotSupportedException($"{type.FullName} not suported"); + default: throw new NotSupportedException($"{type.FullName} not supported"); } } @@ -287,9 +293,9 @@ private void OpenBackup_Click() private async Task OpenImportWizardAsync(WizardTypes bankType) { - var fileExtention = EnumDescriptionConverter.GetEnumDescription(bankType); - var fileName = dialogWrapper.OpenFileDialog(fileExtention); - Logger.Info($"{fileExtention} fileName -> {fileName}"); + var fileExtension = EnumDescriptionConverter.GetEnumDescription(bankType); + var fileName = dialogWrapper.OpenFileDialog(fileExtension); + Logger.Info($"{fileExtension} fileName -> {fileName}"); if (!string.IsNullOrEmpty(fileName)) { var importHelper = this.bankFactory.CreateBankHelper(bankType); @@ -329,7 +335,7 @@ private async Task OpenImportWizardAsync(WizardTypes bankType) this.dialogWrapper.ShowMessageBox( $"Imported {monoToImport.Count} transactions." - + ((duplicatesCount > 0) ? $" Skiped {duplicatesCount} duplicates." : string.Empty), + + ((duplicatesCount > 0) ? $" Skipped {duplicatesCount} duplicates." : string.Empty), $"{importHelper.BankTitle} Import"); Logger.Info($"Imported {monoToImport.Count} transactions. Found duplicates : {duplicatesCount}"); diff --git a/src/Financier.Desktop/Pages/AccountsVM.cs b/src/Financier.Desktop/Pages/AccountsVM.cs index b387439c..2f2ca807 100644 --- a/src/Financier.Desktop/Pages/AccountsVM.cs +++ b/src/Financier.Desktop/Pages/AccountsVM.cs @@ -27,7 +27,7 @@ protected override async Task RefreshData() { using var uow = db.CreateUnitOfWork(); var accountRepo = uow.GetRepository(); - var items = await accountRepo.FindManyAsync( + var items = await accountRepo.FindManyAndProjectAsync( predicate: x => true, projection: acc => new AccountModel(acc), includes : x => x.Currency); diff --git a/src/Financier.Desktop/Pages/BlotterVM.cs b/src/Financier.Desktop/Pages/BlotterVM.cs index 9c7d2017..9c218654 100644 --- a/src/Financier.Desktop/Pages/BlotterVM.cs +++ b/src/Financier.Desktop/Pages/BlotterVM.cs @@ -80,7 +80,7 @@ public PeriodType PeriodType public AccountFilterModel Account { - get => _account ??= DbManual.Account.FirstOrDefault(p => !p.Id.HasValue); + get => _account ??= DbManual.Account.Find(p => !p.Id.HasValue); set { _account = value; @@ -90,7 +90,7 @@ public AccountFilterModel Account public CategoryModel Category { - get => _category ??= DbManual.Category.FirstOrDefault(p => !p.Id.HasValue); + get => _category ??= DbManual.Category.Find(p => !p.Id.HasValue); set { _category = value; @@ -100,7 +100,7 @@ public CategoryModel Category public PayeeModel Payee { - get => _payee ??= DbManual.Payee.FirstOrDefault(p => !p.Id.HasValue); + get => _payee ??= DbManual.Payee.Find(p => !p.Id.HasValue); set { _payee = value; @@ -110,7 +110,7 @@ public PayeeModel Payee public ProjectModel Project { - get => _project ??= DbManual.Project.FirstOrDefault(p => !p.Id.HasValue); + get => _project ??= DbManual.Project.Find(p => !p.Id.HasValue); set { _project = value; @@ -120,7 +120,7 @@ public ProjectModel Project public LocationModel Location { - get => _location ??= DbManual.Location.FirstOrDefault(p => !p.Id.HasValue); + get => _location ??= DbManual.Location.Find(p => !p.Id.HasValue); set { _location = value; @@ -175,32 +175,41 @@ private async Task DeleteTransaction(int id) } } - protected override Task OnEdit(BlotterModel item) + protected override async Task OnEdit(BlotterModel item) { if (item.Type == "Transfer") { - return OpenTransferDialogAsync(item.Id, false); + var transfer = await GetTransfer(item.Id); + await OpenTransferDialogAsync(transfer); } else { - return OpenTransactionDialogAsync(item.Id, false); + var t = await GetTransaction(item.Id); + await OpenTransactionDialogAsync(t.transaction, t.subTransactions); } } - private Task AddTransfer() + private async Task AddTransfer() { - return OpenTransferDialogAsync(0, false); + Transaction transfer = await db.GetOrCreateTransactionAsync(0); + if (Account?.Id != null) + { + transfer.FromAccountId = (int)Account.Id; + } + await OpenTransferDialogAsync(transfer); } - private Task OnDuplicate(BlotterModel item) + private async Task OnDuplicate(BlotterModel item) { if (item.Type == "Transfer") { - return OpenTransferDialogAsync(item.Id, true); + var transfer = await GetTransfer(item.Id, true); + await OpenTransferDialogAsync(transfer); } else { - return OpenTransactionDialogAsync(item.Id, true); + var t = await GetTransaction(item.Id, true); + await OpenTransactionDialogAsync(t.transaction, t.subTransactions); } } @@ -210,25 +219,21 @@ protected override void OnSelectedValueChanged() base.OnSelectedValueChanged(); } - protected override Task OnAdd() + protected override async Task OnAdd() { - return OpenTransactionDialogAsync(0, false); - } + Transaction transaction = await db.GetOrCreateTransactionAsync(0); + IEnumerable subTransactions = await db.GetSubTransactionsAsync(0); - private async Task OpenTransferDialogAsync(int id, bool isDuplicate) - { - Logger.Info($"OpenTransferDialog : id {id} ; isDuplicate {isDuplicate}"); - Transaction transfer = await db.GetOrCreateTransactionAsync(id); - if (isDuplicate) - { - transfer.Id = 0; - transfer.DateTime = UnixTimeConverter.ConvertBack(DateTime.Now); - } - if (id == 0 && Account?.Id != null) + if (Account?.Id != null) { - transfer.FromAccountId = (int)Account.Id; + transaction.FromAccountId = (int)Account.Id; } + await OpenTransactionDialogAsync(transaction, subTransactions); + } + + private async Task OpenTransferDialogAsync(Transaction transfer) + { TransferControlVM dialogVm = new TransferControlVM(new TransferDto(transfer)); var result = dialogWrapper.ShowDialog(dialogVm, 385, 340, "Transfer"); @@ -245,9 +250,20 @@ private async Task OpenTransferDialogAsync(int id, bool isDuplicate) } } - private async Task OpenTransactionDialogAsync(int id, bool isDuplicate) + private async Task GetTransfer(int id, bool isDuplicate = false) + { + Transaction transfer = await db.GetOrCreateTransactionAsync(id); + if (isDuplicate) + { + transfer.Id = 0; + transfer.DateTime = UnixTimeConverter.ConvertBack(DateTime.Now); + } + + return transfer; + } + + private async Task<(Transaction transaction, IEnumerable subTransactions)> GetTransaction(int id, bool isDuplicate = false) { - Logger.Info($"OpenTransactionDialog: id {id} ; isDuplicate {isDuplicate}"); Transaction transaction = await db.GetOrCreateTransactionAsync(id); IEnumerable subTransactions = await db.GetSubTransactionsAsync(id); @@ -256,21 +272,13 @@ private async Task OpenTransactionDialogAsync(int id, bool isDuplicate) transaction.Id = 0; transaction.DateTime = UnixTimeConverter.ConvertBack(DateTime.Now); } - if (id == 0 && Account?.Id != null) - { - transaction.FromAccountId = (int)Account.Id; - } - var transactionDto = new TransactionDto(transaction, subTransactions); + return (transaction, subTransactions); + } - // if transaction not in home currency, replace FromAmount with OriginalFromAmount to show correct values - if (transactionDto.IsOriginalFromAmountVisible) - { - foreach (var item in transactionDto.SubTransactions.OfType()) - { - item.FromAmount = item.OriginalFromAmount ?? 0; - } - } + private async Task OpenTransactionDialogAsync(Transaction transaction, IEnumerable subTransactions) + { + var transactionDto = new TransactionDto(transaction, subTransactions); TransactionControlVM dialogVm = new TransactionControlVM(transactionDto, dialogWrapper); @@ -279,78 +287,99 @@ private async Task OpenTransactionDialogAsync(int id, bool isDuplicate) var resultVm = result as TransactionDto; if (resultVm != null) { - var resultTransactions = new List(); + await SaveTransactionResult(transaction, subTransactions, resultVm); + } + } - MapperHelper.MapTransaction(resultVm, transaction); - long totalFromAmountHomeCurrency = transaction.FromAmount; - resultTransactions.Add(transaction); - if (resultVm?.SubTransactions?.Any() == true) - { - // TODO : Add Unit Test for code below - foreach (var subTransactionDto in resultVm.SubTransactions.OfType()) - { - var subTransaction = await db.GetOrCreateAsync(subTransactionDto.Id); - subTransactionDto.Date = resultVm.Date; - subTransactionDto.Time = resultVm.Time; - MapperHelper.MapTransaction(subTransactionDto, subTransaction); - subTransaction.Parent = transaction; - subTransaction.FromAccountId = transaction.FromAccountId; - subTransaction.OriginalCurrencyId = transaction.OriginalCurrencyId ?? transaction.FromAccount.CurrencyId; - subTransaction.Category = default; - - //Set FromAmount in home currency - if (resultVm.IsOriginalFromAmountVisible) - { - var originalFromAmount = (subTransactionDto).RealFromAmount; - subTransaction.FromAmount = (long)(originalFromAmount * resultVm.Rate); - subTransaction.OriginalFromAmount = originalFromAmount; - totalFromAmountHomeCurrency -= subTransaction.FromAmount; - } + private async Task SaveTransactionResult(Transaction transaction, IEnumerable subTransactions, TransactionDto resultVm) + { + var resultTransactions = new List(); - resultTransactions.Add(subTransaction); - } + MapperHelper.MapTransaction(resultVm, transaction); + long totalFromAmountHomeCurrency = transaction.FromAmount; + resultTransactions.Add(transaction); + if (resultVm.SubTransactions?.Any() == true) + { + // TODO : Add Unit Test for code below + foreach (var subTransactionDto in resultVm.SubTransactions.OfType()) + { + Transaction subTransaction = await GetSubTransaction(transaction, resultVm, subTransactionDto); - if (!resultVm.IsOriginalFromAmountVisible) + //Set FromAmount in home currency + if (resultVm.IsOriginalFromAmountVisible) { - foreach (var subTranfer in resultVm.SubTransactions.OfType()) - { - var subTransaction = await db.GetOrCreateAsync(subTranfer.Id); - - subTranfer.Date = resultVm.Date; - subTranfer.Time = resultVm.Time; - MapperHelper.MapTransfer(subTranfer, subTransaction); - subTransaction.Parent = transaction; - subTransaction.FromAccountId = transaction.FromAccountId; - - resultTransactions.Add(subTransaction); - } + var originalFromAmount = (subTransactionDto).RealFromAmount; + subTransaction.FromAmount = (long)(originalFromAmount * resultVm.Rate); + subTransaction.OriginalFromAmount = originalFromAmount; + totalFromAmountHomeCurrency -= subTransaction.FromAmount; } - // check if sum of all subtransaction == parentTransaction.FromAmount - // if not - add diference to last transaction - if (resultVm.IsOriginalFromAmountVisible && totalFromAmountHomeCurrency != 0) - { - resultTransactions.Last().FromAmount += totalFromAmountHomeCurrency; - } + resultTransactions.Add(subTransaction); } - await db.InsertOrUpdateAsync(resultTransactions); - - var transactionsIds = resultTransactions.Select(x => x.Id).Distinct().ToList(); - var deletedSubTransaction = subTransactions.Select(x => x.Id).Where(x => !transactionsIds.Contains(x)); - foreach (var t in deletedSubTransaction) + if (!resultVm.IsOriginalFromAmountVisible) { - await DeleteTransaction(t); + foreach (var subTranfer in resultVm.SubTransactions.OfType()) + { + Transaction subTransaction = await GetSubTransfer(transaction, resultVm, subTranfer); + resultTransactions.Add(subTransaction); + } } - await db.RebuildAccountBalanceAsync(transaction.FromAccountId); - var toAccounts = resultTransactions.Select(x => x.ToAccountId).Where(x => x > 0).Distinct().ToList(); - foreach (var account in toAccounts) + // check if sum of all subtransaction == parentTransaction.FromAmount + // if not - add diference to last transaction + if (resultVm.IsOriginalFromAmountVisible && totalFromAmountHomeCurrency != 0) { - await db.RebuildAccountBalanceAsync(account); + resultTransactions.Last().FromAmount += totalFromAmountHomeCurrency; } - await RefreshData(); } + + await db.InsertOrUpdateAsync(resultTransactions); + + await ProcessDeletedTransactions(subTransactions, resultTransactions); + + await db.RebuildAccountBalanceAsync(transaction.FromAccountId); + var toAccounts = resultTransactions.Select(x => x.ToAccountId).Where(x => x > 0).Distinct().ToList(); + foreach (var account in toAccounts) + { + await db.RebuildAccountBalanceAsync(account); + } + await RefreshData(); + } + + private async Task ProcessDeletedTransactions(IEnumerable subTransactions, List resultTransactions) + { + var transactionsIds = resultTransactions.Select(x => x.Id).Distinct().ToList(); + var deletedSubTransaction = subTransactions.Select(x => x.Id).Where(x => !transactionsIds.Contains(x)); + foreach (var t in deletedSubTransaction) + { + await DeleteTransaction(t); + } + } + + private async Task GetSubTransfer(Transaction transaction, TransactionDto resultVm, TransferDto subTranfer) + { + var subTransaction = await db.GetOrCreateAsync(subTranfer.Id); + + subTranfer.Date = resultVm.Date; + subTranfer.Time = resultVm.Time; + MapperHelper.MapTransfer(subTranfer, subTransaction); + subTransaction.Parent = transaction; + subTransaction.FromAccountId = transaction.FromAccountId; + return subTransaction; + } + + private async Task GetSubTransaction(Transaction transaction, TransactionDto resultVm, TransactionDto subTransactionDto) + { + var subTransaction = await db.GetOrCreateAsync(subTransactionDto.Id); + subTransactionDto.Date = resultVm.Date; + subTransactionDto.Time = resultVm.Time; + MapperHelper.MapTransaction(subTransactionDto, subTransaction); + subTransaction.Parent = transaction; + subTransaction.FromAccountId = transaction.FromAccountId; + subTransaction.OriginalCurrencyId = transaction.OriginalCurrencyId ?? transaction.FromAccount.CurrencyId; + subTransaction.Category = default; + return subTransaction; } protected override async Task RefreshData() @@ -387,7 +416,7 @@ protected override async Task RefreshData() predicate = predicate.And(x => x.LocationId == _location.Id); } - var items = await repo.FindManyAsync( + var items = await repo.FindManyAndProjectAsync( predicate: predicate, projection: x => new BlotterModel { @@ -429,7 +458,9 @@ protected override async Task RefreshData() Symbol = x.OriginalCurrency.Symbol, } }, - includes: new Expression>[] { x => x.FromAccountCurrency, x => x.ToAccountCurrency, x => x.OriginalCurrency }); + x => x.FromAccountCurrency, + x => x.ToAccountCurrency, + x => x.OriginalCurrency); if (items != null) { diff --git a/src/Financier.Desktop/Pages/CategoriesVM.cs b/src/Financier.Desktop/Pages/CategoriesVM.cs index 2a24f455..bfe4b8dd 100644 --- a/src/Financier.Desktop/Pages/CategoriesVM.cs +++ b/src/Financier.Desktop/Pages/CategoriesVM.cs @@ -37,7 +37,7 @@ private void InitializeNodes(List nodes, List { foreach (var category in categories.OrderBy(x => x.Left)) { - if (!nodes.Any(x => x.Right > category.Left)) + if (!nodes.Exists(x => x.Right > category.Left)) { var subNode = new CategoryTreeModel { @@ -49,7 +49,7 @@ private void InitializeNodes(List nodes, List nodes.Add(subNode); var sub = categories.Where(x => x.Left > category.Left && x.Right < category.Right).ToList(); - if (sub.Any()) + if (sub.Count > 0) { InitializeNodes(subNode.SubCategoties, sub, level + 1); } diff --git a/src/Financier.Desktop/Pages/Dialogs/TransactionControlVM.cs b/src/Financier.Desktop/Pages/Dialogs/TransactionControlVM.cs index d389eb1e..97f26f81 100644 --- a/src/Financier.Desktop/Pages/Dialogs/TransactionControlVM.cs +++ b/src/Financier.Desktop/Pages/Dialogs/TransactionControlVM.cs @@ -51,7 +51,7 @@ public TransactionControlVM( private static void CopySubTransaction(TransactionDto original, TransactionDto modifiedCopy) { original.CategoryId = modifiedCopy.CategoryId; - original.Category = DbManual.Category?.FirstOrDefault(x => x.Id == modifiedCopy.CategoryId); + original.Category = DbManual.Category?.Find(x => x.Id == modifiedCopy.CategoryId); original.FromAmount = modifiedCopy.RealFromAmount; original.IsAmountNegative = modifiedCopy.IsAmountNegative; original.Note = modifiedCopy.Note; @@ -83,7 +83,7 @@ private void ShowRecepiesDialog() { foreach (var item in outputTransactions) { - item.Category = DbManual.Category?.FirstOrDefault(x => x.Id == item.CategoryId); + item.Category = DbManual.Category?.Find(x => x.Id == item.CategoryId); Transaction.SubTransactions.Add(item); } Transaction.RecalculateUnSplitAmount(); diff --git a/src/Financier.Desktop/Pages/ExchangeRatesVM.cs b/src/Financier.Desktop/Pages/ExchangeRatesVM.cs index 26d7f006..db4b4662 100644 --- a/src/Financier.Desktop/Pages/ExchangeRatesVM.cs +++ b/src/Financier.Desktop/Pages/ExchangeRatesVM.cs @@ -65,7 +65,7 @@ protected override async Task RefreshData() { using var uow = db.CreateUnitOfWork(); var accountRepo = uow.GetRepository(); - var items = await accountRepo.FindManyAsync( + var items = await accountRepo.FindManyAndProjectAsync( x => x.FromCurrencyId == (_from != null ? _from.Id : 0) && x.ToCurrencyId == (_to != null ? _to.Id : 0), // where rate => new ExchangeRateModel { diff --git a/src/Financier.Desktop/Properties/GlobalAssemblyInfo.cs b/src/Financier.Desktop/Properties/GlobalAssemblyInfo.cs index 05bddc9c..4112f6bd 100644 --- a/src/Financier.Desktop/Properties/GlobalAssemblyInfo.cs +++ b/src/Financier.Desktop/Properties/GlobalAssemblyInfo.cs @@ -1,9 +1,9 @@ -//2023.11.28.1 +//2024.1.18.4 using System.Reflection; [assembly: AssemblyCompany("Financier.Desktop")] [assembly: AssemblyProduct("Financier.Desktop")] [assembly: AssemblyTitle("Financier.Desktop")] [assembly: AssemblyConfiguration("Release")] -[assembly: AssemblyFileVersion("2023.11.28.1")] -[assembly: AssemblyVersion("2023.11.28.1")] +[assembly: AssemblyFileVersion("2024.1.18.4")] +[assembly: AssemblyVersion("2024.1.18.4")] diff --git a/src/Financier.Desktop/Properties/PublishProfiles/FolderProfile.pubxml b/src/Financier.Desktop/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 00000000..9c89fbed --- /dev/null +++ b/src/Financier.Desktop/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,18 @@ + + + + + Release + Any CPU + bin\Release\publish\win-x64\ + FileSystem + <_TargetId>Folder + net8.0-windows7.0 + win-x64 + false + true + true + + \ No newline at end of file diff --git a/src/Financier.Desktop/Properties/Settings.Designer.cs b/src/Financier.Desktop/Properties/Settings.Designer.cs index 64e83e63..b1e66630 100644 --- a/src/Financier.Desktop/Properties/Settings.Designer.cs +++ b/src/Financier.Desktop/Properties/Settings.Designer.cs @@ -1,26 +1,22 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ +using System.Configuration; + +namespace Financier.Desktop.Properties +{ -namespace Financier.Desktop.Properties { - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.0.3.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default { - get { - return defaultInstance; - } + + public static Settings Default { get { return defaultInstance; } } + + [UserScopedSetting] + public string DefaultBackupDir + { + get => (string)this[nameof(DefaultBackupDir)]; + set => this[nameof(DefaultBackupDir)] = (object)value; } } } diff --git a/src/Financier.Desktop/Wizards/FinancierTransactionDTO.cs b/src/Financier.Desktop/Wizards/FinancierTransactionDTO.cs index 5f47418e..88d0060f 100644 --- a/src/Financier.Desktop/Wizards/FinancierTransactionDTO.cs +++ b/src/Financier.Desktop/Wizards/FinancierTransactionDTO.cs @@ -9,6 +9,7 @@ public class FinancierTransactionDto : BindableBase private string note; private int order; private int projectId; + private int payeeId; private int toAccountId; public int CategoryId @@ -73,6 +74,15 @@ public int ProjectId RaisePropertyChanged(nameof(ProjectId)); } } + public int PayeeId + { + get => payeeId; + set + { + payeeId = value; + RaisePropertyChanged(nameof(PayeeId)); + } + } public int ToAccountId { diff --git a/src/Financier.Desktop/Wizards/MonoWizard/MonoWizardVM.cs b/src/Financier.Desktop/Wizards/MonoWizard/MonoWizardVM.cs index 9f299d43..9fa5244d 100644 --- a/src/Financier.Desktop/Wizards/MonoWizard/MonoWizardVM.cs +++ b/src/Financier.Desktop/Wizards/MonoWizard/MonoWizardVM.cs @@ -86,6 +86,7 @@ private Transaction TransformMonoTransaction(FinancierTransactionDto x) Note = x.Note, LocationId = x.LocationId, ProjectId = x.ProjectId, + PayeeId = x.PayeeId, CategoryId = 0, Category = default, DateTime = x.DateTime, diff --git a/src/Financier.Desktop/Wizards/MonoWizard/Page2VM.cs b/src/Financier.Desktop/Wizards/MonoWizard/Page2VM.cs index 4167c279..d6732536 100644 --- a/src/Financier.Desktop/Wizards/MonoWizard/Page2VM.cs +++ b/src/Financier.Desktop/Wizards/MonoWizard/Page2VM.cs @@ -57,8 +57,8 @@ public AccountFilterModel MonoAccount public List GetMonoTransactions() { - var startDate = _startTransaction?.Date ?? new DateTime(2017, 11, 17); // Monobank launched - return allTransactions.OrderByDescending(x => x.Date).Where(x => x.Date > startDate).ToList(); + var startDate = _startTransaction?.Date ?? new DateTime(2017, 11, 17, 0, 0, 0, DateTimeKind.Local); // Monobank launched + return allTransactions.Where(x => x.Date > startDate).OrderByDescending(x => x.Date).ToList(); } public BankTransaction StartTransaction diff --git a/src/Financier.Desktop/Wizards/MonoWizard/Page3.xaml b/src/Financier.Desktop/Wizards/MonoWizard/Page3.xaml index fa46425c..fdc3e2c0 100644 --- a/src/Financier.Desktop/Wizards/MonoWizard/Page3.xaml +++ b/src/Financier.Desktop/Wizards/MonoWizard/Page3.xaml @@ -238,6 +238,22 @@ + + + + + + + diff --git a/src/Financier.Desktop/Wizards/WizardWindow.xaml b/src/Financier.Desktop/Wizards/WizardWindow.xaml index d3952b62..08cec8c7 100644 --- a/src/Financier.Desktop/Wizards/WizardWindow.xaml +++ b/src/Financier.Desktop/Wizards/WizardWindow.xaml @@ -2,7 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="{Binding Title}" - Width="1040" + Width="1100" Height="620" Background="{DynamicResource WindowBackgroundBrush}" UseLayoutRounding="True" @@ -31,8 +31,9 @@ - + - net6.0-windows + net8.0-windows7.0 enable true diff --git a/src/Financier.Reports/Generic.xaml b/src/Financier.Reports/Generic.xaml index c4b7369c..384d8f6e 100644 --- a/src/Financier.Reports/Generic.xaml +++ b/src/Financier.Reports/Generic.xaml @@ -17,8 +17,8 @@