Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions src/SimpleAccounting/Abstractions/DotNetProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace lg2de.SimpleAccounting.Abstractions;
[ExcludeFromCodeCoverage(
Justification = "The abstraction is for unit testing only. This is the simple implementation.")]
[UsedImplicitly]
internal class DotNetProcess : IProcess
internal partial class DotNetProcess : IProcess
{
public Process? GetProcessByName(string processName)
{
Expand Down Expand Up @@ -74,7 +74,7 @@ public void ShellExecute(string fileName)

[ExcludeFromCodeCoverage(Justification = "The WinAPI cannot be tested.")]
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Names are following external API.")]
private static class WinApi
private static partial class WinApi
{
private const int SW_MINIMIZE = 6;
private const int SW_RESTORE = 9;
Expand All @@ -96,13 +96,16 @@ public static void MinimizeProcess(Process process)
ShowWindow(handle, SW_MINIMIZE);
}

[DllImport("User32.dll")]
private static extern bool SetForegroundWindow(IntPtr handle);
[LibraryImport("User32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool SetForegroundWindow(IntPtr handle);

[DllImport("User32.dll")]
private static extern bool ShowWindow(IntPtr handle, int nCmdShow);
[LibraryImport("User32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool ShowWindow(IntPtr handle, int nCmdShow);

[DllImport("User32.dll")]
private static extern bool IsIconic(IntPtr handle);
[LibraryImport("User32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool IsIconic(IntPtr handle);
}
}
6 changes: 6 additions & 0 deletions src/SimpleAccounting/Abstractions/SystemClock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ namespace lg2de.SimpleAccounting.Abstractions;
[SuppressMessage(
"Major Code Smell", "S6354:Use a testable date/time provider",
Justification = "This is the one and only implementation according pattern.")]
[SuppressMessage(
"Major Code Smell", "S6563:Use UTC when recording DateTime instants",
Justification = "The application works with local calendar dates.")]
[SuppressMessage(
"Major Code Smell", "S6566:Use DateTimeOffset instead of DateTime",
Justification = "The application works with local calendar dates.")]
internal class SystemClock : IClock
{
public DateTime Now()
Expand Down
17 changes: 11 additions & 6 deletions src/SimpleAccounting/Extensions/DateTimeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,22 @@ internal static class DateTimeExtensions
public static DateTime ToDateTime(this uint date)
{
// converts from date format yyyymmdd (as uint) into Date(Time) instance
return new DateTime(
(int)date / YearFactor,
(int)(date / MonthFactor) % MonthFactor,
(int)date % MonthFactor,
0, 0, 0,
DateTimeKind.Local);
return new DateOnly(
(int)date / YearFactor,
(int)(date / MonthFactor) % MonthFactor,
(int)date % MonthFactor)
.ToDateTime(TimeOnly.MinValue, DateTimeKind.Local);
}

public static uint ToAccountingDate(this DateTime date)
{
// converts from Date(Time) instance into date formatted as yyyymmdd (uint)
return DateOnly.FromDateTime(date).ToAccountingDate();
}

public static uint ToAccountingDate(this DateOnly date)
{
// converts from Date instance into date formatted as yyyymmdd (uint)
return (uint)(date.Year * YearFactor + date.Month * MonthFactor + date.Day);
}
}
5 changes: 2 additions & 3 deletions src/SimpleAccounting/Model/AccountingData.Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -351,9 +351,8 @@ public static AccountingDataJournal GetOrCreateLatest(this IList<AccountingDataJ
new AccountingDataJournal
{
Year = now.Year.ToString(CultureInfo.InvariantCulture),
DateStart = new DateTime(now.Year, 1, 1, 0, 0, 0, DateTimeKind.Local).ToAccountingDate(),
DateEnd = new DateTime(now.Year, december, decemberLast, 0, 0, 0, DateTimeKind.Local)
.ToAccountingDate(),
DateStart = new DateOnly(now.Year, 1, 1).ToAccountingDate(),
DateEnd = new DateOnly(now.Year, december, decemberLast).ToAccountingDate(),
Booking = []
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/SimpleAccounting/Model/ProjectData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
private Task autoSaveTask = Task.CompletedTask;
private CancellationTokenSource? cancellationTokenSource;
private IDisposable? fileMonitoringHandle;
private DateTime lastNotification = DateTime.MinValue;
private DateTime lastNotification;
private TaskCompletionSource? savingCompleted;
private AccountingData storage;

Expand Down Expand Up @@ -422,7 +422,7 @@
string newFileName =
Path.GetFileNameWithoutExtension(this.FileName)
+ "_"
+ this.clock.Now().ToString("yyyyMMddHHmmss", CultureInfo.CurrentUICulture);

Check warning on line 425 in src/SimpleAccounting/Model/ProjectData.cs

View workflow job for this annotation

GitHub Actions / build

Do not hardcode the format specifier.
this.FileName = Path.Combine(Path.GetDirectoryName(this.FileName)!, newFileName + ".acml");
await this.SaveProjectAsync();
await this.LoadFromFileAsync(this.FileName);
Expand Down
2 changes: 1 addition & 1 deletion src/SimpleAccounting/Presentation/AccountViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public bool IsImportActive
internal static void ResetTypesLazy()
{
Types.Clear();
foreach (var type in Enum.GetValues(typeof(AccountDefinitionType)).Cast<AccountDefinitionType>())
foreach (var type in Enum.GetValues<AccountDefinitionType>())
{
var localizedType = Resources.ResourceManager.GetString($"AccountType_{type}") ?? $"<{type}>";
Types[type] = localizedType;
Expand Down
143 changes: 70 additions & 73 deletions src/SimpleAccounting/Presentation/AccountsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ internal class AccountsViewModel : Screen, IAccountsViewModel
private readonly IProjectData projectData;
private readonly IWindowManager windowManager;

private AccountViewModel? selectedAccount;

public AccountsViewModel(IWindowManager windowManager, IProjectData projectData)
{
this.windowManager = windowManager;
Expand All @@ -36,10 +34,10 @@ public AccountsViewModel(IWindowManager windowManager, IProjectData projectData)

public AccountViewModel? SelectedAccount
{
get => this.selectedAccount;
get;
set
{
this.selectedAccount = value;
field = value;
this.NotifyOfPropertyChange();
}
}
Expand Down Expand Up @@ -206,6 +204,72 @@ public Task OnEditAccountAsync(object? commandParameter)
return this.ShowEditAccountDialogAsync(account);
}

private static void SaveImportConfiguration(AccountViewModel viewModel, AccountDefinition accountDefinition)
{
if (!viewModel.IsImportActive)
{
accountDefinition.ImportMapping = null;
return;
}

// save required items
accountDefinition.ImportMapping = new AccountDefinitionImportMapping
{
Columns =
[
new AccountDefinitionImportMappingColumnsColumn
{
Target = AccountDefinitionImportMappingColumnsColumnTarget.Date,
Source = viewModel.ImportDateSource,
IgnorePattern = viewModel.ImportDateIgnorePattern
},
new AccountDefinitionImportMappingColumnsColumn
{
Target = AccountDefinitionImportMappingColumnsColumnTarget.Value,
Source = viewModel.ImportValueSource,
IgnorePattern = viewModel.ImportValueIgnorePattern
}
]
};

// save optional items
if (!string.IsNullOrWhiteSpace(viewModel.ImportTextSource))
{
accountDefinition.ImportMapping.Columns.Add(
new AccountDefinitionImportMappingColumnsColumn
{
Target = AccountDefinitionImportMappingColumnsColumnTarget.Text,
Source = viewModel.ImportTextSource,
IgnorePattern = viewModel.ImportTextIgnorePattern
});
}

if (!string.IsNullOrWhiteSpace(viewModel.ImportNameSource))
{
accountDefinition.ImportMapping.Columns.Add(
new AccountDefinitionImportMappingColumnsColumn
{
Target = AccountDefinitionImportMappingColumnsColumnTarget.Name,
Source = viewModel.ImportNameSource,
IgnorePattern = viewModel.ImportNameIgnorePattern
});
}

if (!viewModel.ImportPatterns.Any())
{
return;
}

accountDefinition.ImportMapping.Patterns = viewModel.ImportPatterns.Select(x =>
new AccountDefinitionImportMappingPatternsPattern
{
Expression = x.Expression,
Value = x.Value?.ToModelValue() ?? 0,
ValueSpecified = x.Value.HasValue,
AccountID = x.Account?.ID ?? 0
}).ToList();
}

private async Task ShowEditAccountDialogAsync(AccountViewModel selectedAccountViewModel)
{
// clone view model for easy rollback
Expand Down Expand Up @@ -263,11 +327,10 @@ private async Task ShowEditAccountDialogAsync(AccountViewModel selectedAccountVi
clonedViewModel.Group!.Account.Add(accountData);

// reorder the accounts in the new group
clonedViewModel.Group!.Account =
clonedViewModel.Group.Account.OrderBy(x => x.ID).ToList();
clonedViewModel.Group.Account = clonedViewModel.Group.Account.OrderBy(x => x.ID).ToList();
}

this.SaveImportConfiguration(clonedViewModel, accountData);
SaveImportConfiguration(clonedViewModel, accountData);

// update view model
selectedAccountViewModel.Name = clonedViewModel.Name;
Expand Down Expand Up @@ -339,72 +402,6 @@ private void UpdateImportCandidateAccounts(AccountViewModel accountViewModel)
}
}

private void SaveImportConfiguration(AccountViewModel viewModel, AccountDefinition accountDefinition)
{
if (!viewModel.IsImportActive)
{
accountDefinition.ImportMapping = null;
return;
}

// save required items
accountDefinition.ImportMapping = new AccountDefinitionImportMapping
{
Columns =
[
new AccountDefinitionImportMappingColumnsColumn
{
Target = AccountDefinitionImportMappingColumnsColumnTarget.Date,
Source = viewModel.ImportDateSource,
IgnorePattern = viewModel.ImportDateIgnorePattern
},
new AccountDefinitionImportMappingColumnsColumn
{
Target = AccountDefinitionImportMappingColumnsColumnTarget.Value,
Source = viewModel.ImportValueSource,
IgnorePattern = viewModel.ImportValueIgnorePattern
}
]
};

// save optional items
if (!string.IsNullOrWhiteSpace(viewModel.ImportTextSource))
{
accountDefinition.ImportMapping.Columns.Add(
new AccountDefinitionImportMappingColumnsColumn
{
Target = AccountDefinitionImportMappingColumnsColumnTarget.Text,
Source = viewModel.ImportTextSource,
IgnorePattern = viewModel.ImportTextIgnorePattern
});
}

if (!string.IsNullOrWhiteSpace(viewModel.ImportNameSource))
{
accountDefinition.ImportMapping.Columns.Add(
new AccountDefinitionImportMappingColumnsColumn
{
Target = AccountDefinitionImportMappingColumnsColumnTarget.Name,
Source = viewModel.ImportNameSource,
IgnorePattern = viewModel.ImportNameIgnorePattern
});
}

if (!viewModel.ImportPatterns.Any())
{
return;
}

accountDefinition.ImportMapping.Patterns = viewModel.ImportPatterns.Select(x =>
new AccountDefinitionImportMappingPatternsPattern
{
Expression = x.Expression,
Value = x.Value?.ToModelValue() ?? 0,
ValueSpecified = x.Value.HasValue,
AccountID = x.Account?.ID ?? 0
}).ToList();
}

private void RefreshAccountList()
{
IEnumerable<AccountViewModel> accounts = this.allAccounts;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace lg2de.SimpleAccounting.Presentation;

using System;
using System.Diagnostics.CodeAnalysis;
using lg2de.SimpleAccounting.Abstractions;
using lg2de.SimpleAccounting.Model;
Expand All @@ -19,15 +18,13 @@ namespace lg2de.SimpleAccounting.Presentation;
[SuppressMessage(
"Major Code Smell", "S4055:Literals should not be passed as localized parameters",
Justification = "Design view model defines useful values")]
[SuppressMessage(
"Major Code Smell",
"S6354:Use a testable date/time provider",
Justification = "Is ok for design view model.")]
internal class EditBookingDesignViewModel : EditBookingViewModel
{
private static readonly SystemClock Clock = new();

public EditBookingDesignViewModel()
: base(
new ProjectData(new Settings(), null!, null!, null!, new SystemClock(), null!), DateTime.Now,
new ProjectData(new Settings(), null!, null!, null!, Clock, null!), Clock.Now(),
editMode: false)
{
this.BookingIdentifier = 42;
Expand Down
22 changes: 10 additions & 12 deletions src/SimpleAccounting/Presentation/ImportBookingsDesignViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,10 @@ namespace lg2de.SimpleAccounting.Presentation;
[SuppressMessage(
"Major Code Smell", "S4055:Literals should not be passed as localized parameters",
Justification = "Design view model defines useful values")]
[SuppressMessage(
"Major Code Smell",
"S6354:Use a testable date/time provider",
Justification = "Is ok for design view model.")]
internal class ImportBookingsDesignViewModel : ImportBookingsViewModel
{
private static readonly SystemClock Clock = new();

// define some accounts
private static readonly List<AccountDefinition> SampleAccounts =
[
Expand Down Expand Up @@ -61,13 +59,13 @@ internal class ImportBookingsDesignViewModel : ImportBookingsViewModel
[
new AccountingDataJournal
{
DateStart = (uint)(DateTime.Today.Year * 10000 + 101),
DateEnd = (uint)(DateTime.Today.Year * 10000 + 1231),
DateStart = (uint)(Clock.Now().Year * 10000 + 101),
DateEnd = (uint)(Clock.Now().Year * 10000 + 1231),
Booking =
[
new AccountingDataJournalBooking
{
Date = (uint)(DateTime.Today.Year * 10000 + 1231),
Date = (uint)(Clock.Now().Year * 10000 + 1231),
ID = 999,
Credit = [new BookingValue { Account = 100, Text = "End of year", Value = 1234 }],
Debit = [new BookingValue { Account = 990, Text = "End of year", Value = 1234 }]
Expand All @@ -79,16 +77,16 @@ internal class ImportBookingsDesignViewModel : ImportBookingsViewModel
};

public ImportBookingsDesignViewModel()
: base(null!, null!, new ProjectData(new Settings(), null!, null!, null!, new SystemClock(), null!))
: base(null!, null!, new ProjectData(new Settings(), null!, null!, null!, Clock, null!))
{
this.ProjectData.LoadData(SampleData);
this.SelectedAccountNumber = 100;
this.StartDate = DateTime.Today;
this.StartDate = Clock.Now().Date;

this.LoadedData.Add(
new ImportEntryViewModel(SampleAccounts)
{
Date = DateTime.Now - TimeSpan.FromDays(1),
Date = Clock.Now() - TimeSpan.FromDays(1),
Name = "Should not be visible!",
Text = "Should not be visible!",
RemoteAccount = SampleAccounts.Single(x => x.ID == 600),
Expand All @@ -97,7 +95,7 @@ public ImportBookingsDesignViewModel()
this.LoadedData.Add(
new ImportEntryViewModel(SampleAccounts)
{
Date = DateTime.Now,
Date = Clock.Now(),
Name = "McX",
Text = "Shoes",
RemoteAccount = SampleAccounts.Single(x => x.ID == 600),
Expand All @@ -107,7 +105,7 @@ public ImportBookingsDesignViewModel()
this.LoadedData.Add(
new ImportEntryViewModel(SampleAccounts)
{
Date = DateTime.Now + TimeSpan.FromDays(1),
Date = Clock.Now() + TimeSpan.FromDays(1),
Name = "McY",
Text = "More Shoes",
Value = 159.95,
Expand Down
Loading