Compare commits
No commits in common. "786fafefa56b5cacb359518698366152799ebb34" and "8926fb1e973ba6629717768432a991dc521d72a1" have entirely different histories.
786fafefa5
...
8926fb1e97
36 changed files with 178 additions and 270 deletions
|
|
@ -103,10 +103,3 @@ Analysis and phased plan produced 2026-07-02. See conversation history for full
|
|||
- **PR F** — Phase 3 item 21: precomputed dictionaries in `ViewService` to eliminate O(N·M) `Single(...)` scans and per-account transaction filtering.
|
||||
- **Item 19 (async I/O)** — deferred. After PR D, each XML file is loaded at most once per SignalR circuit, and this app is single-user local Blazor Server. Converting every repository/service method to async would touch ~40 files for negligible user-visible benefit and real regression risk. Revisit if the app is ever hosted for multiple concurrent users.
|
||||
- **PR G** — Phase 4 items 23 + 24: `Schaad.Accounting.Tests` xUnit + NSubstitute + Shouldly project. Adopts the `xxxTestShould.DoThisWhenThat` naming convention with Shouldly assertions (no xUnit `Assert.*`). Covers `ViewService`, `TransactionRepository`, `Formatting`, `RepositoryCache`, `AccountRepository`, `ChartService`, and `FileService.GetTransactionListCsv` — 38 tests locking in the earlier PRs' behavior. Note: originally shipped as three separate commits (initial project, expanded coverage, Shouldly + naming conversion) and later squashed into one commit at the user's request.
|
||||
- **PR H** — Phase 2 item 18 (first slice): add `ILogger<FileService>` to `FileService.ImportAccountStatementFile` so bank-statement imports emit `Information` for the file being processed and each account's import count, `Warning` when an account is skipped because it belongs to a different mandator, and `Error` when the vendor parser reports a failure. Rest of the logging (BaseRepository save failures, MatchOpenBankTransactions summary) tracked as a follow-up because it requires threading loggers through all seven repositories.
|
||||
- **PR I** — Phase 2 item 12: move the service interfaces (`IViewService`, `IFileService`, `IChartService`) from `Schaad.Accounting.Common` into `Schaad.Accounting.Services/Interfaces/`. Namespaces are unchanged (`Schaad.Accounting.Interfaces`), so no consumer needs a `using` update. Drops the `Schaad.Finance.Api` `<Reference>` from `Common.csproj` — Common is now vendor-free and matches its documented role as the "shared models, DTOs, interfaces" layer.
|
||||
- **PR J** — Phase 2 item 18 (second slice): thread `ILogger<T>` through `BaseRepository` and every concrete repository so `BaseRepository.Save`'s catch block logs the failing file path and exception before rethrowing (previously the exception's origin was silently swallowed and only the stack trace at the callsite survived). Pulls `Microsoft.Extensions.Logging.Abstractions` into the Db project. `TransactionRepositoryTestShould` and `AccountRepositoryTestShould` use `NullLogger<T>.Instance`. Remaining item 18 work (MatchOpenBankTransactions match-count summary in `ViewService`) still open.
|
||||
- **PR K** — Phase 5 item 27: introduce `IFxConverter.ConvertToChf(amount, fromCurrency)` and its `FxConverter` implementation. The vendor `IFxService` and the FixerIo API key are now hidden inside `FxConverter`; callers stop threading the API key through every method call. `ViewService` drops both `IFxService` and `ISettingsService` from its constructor and takes `IFxConverter` instead. Simplifies `GetAccountViewList` (no more per-call `settingsService.GetSettings()` reads) and `GetBalanceSheetView`. Two-test `FxConverterTestShould` locks in the target-currency and API-key routing.
|
||||
- **PR L** — Phase 2 item 10: drop the hand-rolled `Copy(target)` methods from `Account`, `BookingRule`, `BookingText`, `SubClass`, and `SplitPredefinition`; keep only a `Transaction.Clone()` for defensive-copy needs in `GetTransaction` / `ViewService.WithDisplaySign`. Every `SaveXxx` now uses `FindIndex` → in-place replace (or `Add` for new entries), moving the two "hidden" defaults (`Account.Currency = "CHF"` when unset, `Transaction.BookingDate = ValueDate` when unset) into the corresponding `SaveXxx` method where they belong. Fixes a pre-existing bug: `Transaction.Copy` never copied `RelatedParty`, so update-saves silently dropped it. Added `PreserveRelatedPartyWhenRoundTrippingTransaction` and `DefaultBookingDateToValueDateWhenBookingDateIsUnset` as regression tests. 42 tests total.
|
||||
- **PR M** — Phase 2 item 18 (final slice): add `ILogger<ViewService>` and log a match-count summary from `MatchOpenBankTransactions` (`"Matched {Matched} of {Total} open bank transactions"`). Completes item 18 — statement imports (PR H), repository save failures (PR J), and match runs now all emit structured logs. Also removes a dead `ISettingsService` injection from `Home.razor.cs` (declared with `[Inject]` but never used anywhere in the file).
|
||||
- **PR N** — Small polish pass covering three long-noted rough edges from the original analysis: `ClassIds` becomes a `static class` (was instantiable); `Home.razor.cs` loses the redundant `loaded` guard in `OnInitializedAsync` (Blazor already runs that lifecycle hook once per component instance); the hardcoded `mandator = "Claudio Schaad"` in `SettingsService` moves to a `DefaultMandator` field on `SettingsDataset`, plumbed through `appsettings.Development.json`.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
namespace Schaad.Accounting
|
||||
{
|
||||
public static class ClassIds
|
||||
public class ClassIds
|
||||
{
|
||||
public const int Activa = 1;
|
||||
public const int Passiva = 2;
|
||||
|
|
|
|||
|
|
@ -5,10 +5,5 @@
|
|||
public string DataPath { get; set; }
|
||||
|
||||
public string FixerIoApiKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Mandator selected when the app starts. Users can switch via the header dropdown.
|
||||
/// </summary>
|
||||
public string DefaultMandator { get; set; } = "Claudio Schaad";
|
||||
}
|
||||
}
|
||||
|
|
@ -59,5 +59,19 @@ namespace Schaad.Accounting.Models
|
|||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,5 +20,18 @@ namespace Schaad.Accounting.Models
|
|||
[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,5 +10,14 @@ namespace Schaad.Accounting.Models
|
|||
[Required]
|
||||
[MinLength(3)]
|
||||
public string Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Makes a copy
|
||||
/// </summary>
|
||||
public void Copy(BookingText target)
|
||||
{
|
||||
target.Id = Id;
|
||||
target.Text = Text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,5 +9,17 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,5 +14,15 @@ namespace Schaad.Accounting.Models
|
|||
[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -74,22 +74,20 @@ namespace Schaad.Accounting.Models
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a shallow independent copy — used to hand out defensive copies
|
||||
/// (e.g. from repository reads) without exposing the caller to later mutation.
|
||||
/// Makes a copy
|
||||
/// </summary>
|
||||
public Transaction Clone() => new()
|
||||
public void Copy(Transaction target)
|
||||
{
|
||||
Id = Id,
|
||||
BankTransactionId = BankTransactionId,
|
||||
BankTransactionText = BankTransactionText,
|
||||
RelatedParty = RelatedParty,
|
||||
OriginAccountId = OriginAccountId,
|
||||
TargetAccountId = TargetAccountId,
|
||||
Text = Text,
|
||||
Value = Value,
|
||||
ValueDate = ValueDate,
|
||||
BookingDate = BookingDate,
|
||||
FxRate = FxRate
|
||||
};
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,4 +4,9 @@
|
|||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RootNamespace>Schaad.Accounting</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Schaad.Finance.Api">
|
||||
<HintPath>..\dlls\Schaad.Finance.Api.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
|
|
@ -12,8 +11,8 @@ namespace Schaad.Accounting.Repositories
|
|||
{
|
||||
private readonly string ACCOUNTS = "Accounts.xml";
|
||||
|
||||
public AccountRepository(ISettingsService settingsService, RepositoryCache cache, ILogger<AccountRepository> logger)
|
||||
: base(settingsService, cache, logger)
|
||||
public AccountRepository(ISettingsService settingsService, RepositoryCache cache)
|
||||
: base(settingsService, cache)
|
||||
{
|
||||
EnsureAccountsFile();
|
||||
}
|
||||
|
|
@ -28,22 +27,16 @@ namespace Schaad.Accounting.Repositories
|
|||
/// </summary>
|
||||
public void SaveAccount(Account account)
|
||||
{
|
||||
if (string.IsNullOrEmpty(account.Currency))
|
||||
{
|
||||
account.Currency = "CHF";
|
||||
}
|
||||
|
||||
var accounts = GetAccountList();
|
||||
var idx = accounts.FindIndex(a => a.Id == account.Id);
|
||||
if (idx >= 0)
|
||||
{
|
||||
accounts[idx] = account;
|
||||
}
|
||||
else
|
||||
var existingAccount = accounts.FirstOrDefault(a => a.Id == account.Id);
|
||||
|
||||
if (existingAccount == null)
|
||||
{
|
||||
existingAccount = new Account();
|
||||
accounts.Add(existingAccount);
|
||||
account.Id = Guid.NewGuid().ToString();
|
||||
accounts.Add(account);
|
||||
}
|
||||
account.Copy(existingAccount);
|
||||
Save(accounts, ACCOUNTS);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Schaad.Accounting.Datasets;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
|
@ -11,8 +10,8 @@ namespace Schaad.Accounting.Repositories
|
|||
{
|
||||
private readonly string BANK_TRANSACTIONS = "BankTransactions.xml";
|
||||
|
||||
public BankTransactionRepository(ISettingsService settingsService, RepositoryCache cache, ILogger<BankTransactionRepository> logger)
|
||||
: base(settingsService, cache, logger)
|
||||
public BankTransactionRepository(ISettingsService settingsService, RepositoryCache cache)
|
||||
: base(settingsService, cache)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using System.IO;
|
|||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
|
||||
namespace Schaad.Accounting.Repositories
|
||||
|
|
@ -19,13 +18,11 @@ namespace Schaad.Accounting.Repositories
|
|||
|
||||
protected readonly ISettingsService settingsService;
|
||||
protected readonly RepositoryCache cache;
|
||||
protected readonly ILogger logger;
|
||||
|
||||
protected BaseRepository(ISettingsService settingsService, RepositoryCache cache, ILogger logger)
|
||||
protected BaseRepository(ISettingsService settingsService, RepositoryCache cache)
|
||||
{
|
||||
this.settingsService = settingsService;
|
||||
this.cache = cache;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
protected void EnsureFileExists(string fileName)
|
||||
|
|
@ -82,9 +79,8 @@ namespace Schaad.Accounting.Repositories
|
|||
|
||||
File.Move(tmpPath, filePath, overwrite: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch
|
||||
{
|
||||
logger.LogError(ex, "Failed to save {FilePath}", filePath);
|
||||
if (File.Exists(tmpPath))
|
||||
{
|
||||
try { File.Delete(tmpPath); } catch { /* best effort */ }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
|
|
@ -11,8 +10,8 @@ namespace Schaad.Accounting.Repositories
|
|||
{
|
||||
private readonly string BOOKING_RULES = "BookingRules.xml";
|
||||
|
||||
public BookingRuleRepository(ISettingsService settingsService, RepositoryCache cache, ILogger<BookingRuleRepository> logger)
|
||||
: base(settingsService, cache, logger)
|
||||
public BookingRuleRepository(ISettingsService settingsService, RepositoryCache cache)
|
||||
: base(settingsService, cache)
|
||||
{
|
||||
EnsureFileExists(BOOKING_RULES);
|
||||
}
|
||||
|
|
@ -28,16 +27,15 @@ namespace Schaad.Accounting.Repositories
|
|||
public void SaveBookingRule(BookingRule bookingRule)
|
||||
{
|
||||
var bookingRules = GetBookingRuleList();
|
||||
var idx = bookingRules.FindIndex(r => r.Id == bookingRule.Id);
|
||||
if (idx >= 0)
|
||||
{
|
||||
bookingRules[idx] = bookingRule;
|
||||
}
|
||||
else
|
||||
var existingRule = bookingRules.FirstOrDefault(a => a.Id == bookingRule.Id);
|
||||
|
||||
if (existingRule == null)
|
||||
{
|
||||
existingRule = new BookingRule();
|
||||
bookingRules.Add(existingRule);
|
||||
bookingRule.Id = Guid.NewGuid().ToString();
|
||||
bookingRules.Add(bookingRule);
|
||||
}
|
||||
bookingRule.Copy(existingRule);
|
||||
Save(bookingRules, BOOKING_RULES);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
|
|
@ -11,8 +10,8 @@ namespace Schaad.Accounting.Repositories
|
|||
{
|
||||
private readonly string BOOKING_TEXTS = "BookingTexts.xml";
|
||||
|
||||
public BookingTextRepository(ISettingsService settingsService, RepositoryCache cache, ILogger<BookingTextRepository> logger)
|
||||
: base(settingsService, cache, logger)
|
||||
public BookingTextRepository(ISettingsService settingsService, RepositoryCache cache)
|
||||
: base(settingsService, cache)
|
||||
{
|
||||
EnsureFileExists(BOOKING_TEXTS);
|
||||
}
|
||||
|
|
@ -28,16 +27,15 @@ namespace Schaad.Accounting.Repositories
|
|||
public void SaveBookingText(BookingText bookingText)
|
||||
{
|
||||
var bookingTexts = GetBookingTextList();
|
||||
var idx = bookingTexts.FindIndex(t => t.Id == bookingText.Id);
|
||||
if (idx >= 0)
|
||||
{
|
||||
bookingTexts[idx] = bookingText;
|
||||
}
|
||||
else
|
||||
var existingText = bookingTexts.FirstOrDefault(a => a.Id == bookingText.Id);
|
||||
|
||||
if (existingText == null)
|
||||
{
|
||||
existingText = new BookingText();
|
||||
bookingTexts.Add(existingText);
|
||||
bookingText.Id = Guid.NewGuid().ToString();
|
||||
bookingTexts.Add(bookingText);
|
||||
}
|
||||
bookingText.Copy(existingText);
|
||||
Save(bookingTexts, BOOKING_TEXTS);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
|
|
@ -11,8 +10,8 @@ namespace Schaad.Accounting.Repositories
|
|||
{
|
||||
private readonly string SPLIT_PREDEFINITION = "SplitPredefinitions.xml";
|
||||
|
||||
public SplitPredefinitionRepository(ISettingsService settingsService, RepositoryCache cache, ILogger<SplitPredefinitionRepository> logger)
|
||||
: base(settingsService, cache, logger)
|
||||
public SplitPredefinitionRepository(ISettingsService settingsService, RepositoryCache cache)
|
||||
: base(settingsService, cache)
|
||||
{
|
||||
EnsureFileExists(SPLIT_PREDEFINITION);
|
||||
}
|
||||
|
|
@ -28,16 +27,15 @@ namespace Schaad.Accounting.Repositories
|
|||
public void SaveSplitPredefinition(SplitPredefinition splitPredefinition)
|
||||
{
|
||||
var definitions = GetSplitPredefinitionList();
|
||||
var idx = definitions.FindIndex(d => d.Id == splitPredefinition.Id);
|
||||
if (idx >= 0)
|
||||
{
|
||||
definitions[idx] = splitPredefinition;
|
||||
}
|
||||
else
|
||||
var existingDefinition = definitions.FirstOrDefault(a => a.Id == splitPredefinition.Id);
|
||||
|
||||
if (existingDefinition == null)
|
||||
{
|
||||
existingDefinition = new SplitPredefinition();
|
||||
definitions.Add(existingDefinition);
|
||||
splitPredefinition.Id = Guid.NewGuid().ToString();
|
||||
definitions.Add(splitPredefinition);
|
||||
}
|
||||
splitPredefinition.Copy(existingDefinition);
|
||||
Save(definitions, SPLIT_PREDEFINITION);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
|
|
@ -12,8 +11,8 @@ namespace Schaad.Accounting.Repositories
|
|||
private readonly Dictionary<int, string> classes = new Dictionary<int, string>();
|
||||
private readonly string SUBCLASSES = "SubClasses.xml";
|
||||
|
||||
public SubclassRepository(ISettingsService settingsService, RepositoryCache cache, ILogger<SubclassRepository> logger)
|
||||
: base(settingsService, cache, logger)
|
||||
public SubclassRepository(ISettingsService settingsService, RepositoryCache cache)
|
||||
: base(settingsService, cache)
|
||||
{
|
||||
EnsureFileExists(SUBCLASSES);
|
||||
|
||||
|
|
@ -29,21 +28,20 @@ namespace Schaad.Accounting.Repositories
|
|||
public List<SubClass> GetSubClassList() => LoadList<SubClass>(SUBCLASSES);
|
||||
|
||||
/// <summary>
|
||||
/// Save a subclass (insert/update)
|
||||
/// Save a booking text (insert/update)
|
||||
/// </summary>
|
||||
public void SaveSubClass(SubClass subClass)
|
||||
{
|
||||
var subclasses = GetSubClassList();
|
||||
var idx = subclasses.FindIndex(s => s.Id == subClass.Id);
|
||||
if (idx >= 0)
|
||||
{
|
||||
subclasses[idx] = subClass;
|
||||
}
|
||||
else
|
||||
var existingSubClass = subclasses.FirstOrDefault(a => a.Id == subClass.Id);
|
||||
|
||||
if (existingSubClass == null)
|
||||
{
|
||||
existingSubClass = new SubClass();
|
||||
subclasses.Add(existingSubClass);
|
||||
subClass.Id = Guid.NewGuid().ToString();
|
||||
subclasses.Add(subClass);
|
||||
}
|
||||
subClass.Copy(existingSubClass);
|
||||
Save(subclasses, SUBCLASSES);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
||||
|
|
@ -12,8 +11,8 @@ namespace Schaad.Accounting.Repositories
|
|||
private readonly IAccountRepository accountRepository;
|
||||
private readonly string TRANSACTIONS = "Transactions.xml";
|
||||
|
||||
public TransactionRepository(ISettingsService settingsService, RepositoryCache cache, IAccountRepository accountRepository, ILogger<TransactionRepository> logger)
|
||||
: base(settingsService, cache, logger)
|
||||
public TransactionRepository(ISettingsService settingsService, RepositoryCache cache, IAccountRepository accountRepository)
|
||||
: base(settingsService, cache)
|
||||
{
|
||||
this.accountRepository = accountRepository;
|
||||
}
|
||||
|
|
@ -45,22 +44,17 @@ namespace Schaad.Accounting.Repositories
|
|||
transaction.FxRate = null;
|
||||
}
|
||||
|
||||
if (transaction.BookingDate <= DateTime.MinValue)
|
||||
{
|
||||
transaction.BookingDate = transaction.ValueDate;
|
||||
}
|
||||
|
||||
var transactionList = GetTransactionList();
|
||||
var idx = transactionList.FindIndex(t => t.Id == transaction.Id);
|
||||
if (idx >= 0)
|
||||
{
|
||||
transactionList[idx] = transaction;
|
||||
}
|
||||
else
|
||||
var existingTransaction = transactionList.FirstOrDefault(a => a.Id == transaction.Id);
|
||||
|
||||
if (existingTransaction == null)
|
||||
{
|
||||
existingTransaction = new Transaction();
|
||||
transactionList.Add(existingTransaction);
|
||||
transaction.Id = Guid.NewGuid().ToString();
|
||||
transactionList.Add(transaction);
|
||||
}
|
||||
transaction.Copy(existingTransaction);
|
||||
Save(transactionList, TRANSACTIONS);
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +70,8 @@ namespace Schaad.Accounting.Repositories
|
|||
return null;
|
||||
}
|
||||
|
||||
var result = stored.Clone();
|
||||
var result = new Transaction();
|
||||
stored.Copy(result);
|
||||
|
||||
// value is stored in CHF -> convert back to foreign currency for display/editing
|
||||
var isFxAccount = accountRepository.GetAccount(result.OriginAccountId).IsFxAccount
|
||||
|
|
|
|||
|
|
@ -7,8 +7,4 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\Schaad.Accounting.Common\Schaad.Accounting.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System.Collections.Generic;
|
|||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Schaad.Accounting.Datasets;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Finance.Api;
|
||||
|
|
@ -18,7 +17,6 @@ namespace Schaad.Accounting.Services
|
|||
private readonly ISettingsService settingsService;
|
||||
private readonly IAccountStatementService accountStatementService;
|
||||
private readonly ICreditCardStatementService creditCardStatementService;
|
||||
private readonly ILogger<FileService> logger;
|
||||
|
||||
public FileService(
|
||||
ISettingsService settingsService,
|
||||
|
|
@ -26,8 +24,7 @@ namespace Schaad.Accounting.Services
|
|||
ITransactionRepository transactionsRepository,
|
||||
IBankTransactionRepository bankTransactionRepository,
|
||||
IAccountStatementService accountStatementService,
|
||||
ICreditCardStatementService creditCardStatementService,
|
||||
ILogger<FileService> logger)
|
||||
ICreditCardStatementService creditCardStatementService)
|
||||
{
|
||||
this.settingsService = settingsService;
|
||||
this.accountRepository = accountRepository;
|
||||
|
|
@ -35,7 +32,6 @@ namespace Schaad.Accounting.Services
|
|||
this.bankTransactionRepository = bankTransactionRepository;
|
||||
this.accountStatementService = accountStatementService;
|
||||
this.creditCardStatementService = creditCardStatementService;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public string Backup()
|
||||
|
|
@ -50,8 +46,6 @@ namespace Schaad.Accounting.Services
|
|||
// http://www.mikesdotnetting.com/article/288/asp-net-5-uploading-files-with-asp-net-mvc-6
|
||||
public IReadOnlyList<MessageDataset> ImportAccountStatementFile(string filePath)
|
||||
{
|
||||
logger.LogInformation("Importing account statement file {FilePath}", filePath);
|
||||
|
||||
var messages = new List<MessageDataset>();
|
||||
var accountStatementResults = accountStatementService.ReadFile(filePath, Encoding.UTF8);
|
||||
|
||||
|
|
@ -62,7 +56,6 @@ namespace Schaad.Accounting.Services
|
|||
|
||||
if (accountStatementResult.IsSuccess == false)
|
||||
{
|
||||
logger.LogError("Statement parse failed for account {AccountNumber}: {Error}", account.AccountNumber, accountStatementResult.Error);
|
||||
messages.Add(new MessageDataset($"Account {account.AccountNumber} NICHT importiert: {accountStatementResult.Error}", MessageStatus.Error));
|
||||
}
|
||||
|
||||
|
|
@ -94,9 +87,6 @@ namespace Schaad.Accounting.Services
|
|||
var count = bankTransactionRepository.SaveBankTransactionList(account.AccountNumber, transactionsThisYear);
|
||||
accountRepository.SaveBankAccountBalance(account.AccountNumber, (decimal)account.EndBalance.Value);
|
||||
|
||||
logger.LogInformation("Imported {Imported} of {Total} transactions for account {AccountNumber}",
|
||||
count, account.Transactions.Count, account.AccountNumber);
|
||||
|
||||
var status = count == account.Transactions.Count ? MessageStatus.Success : MessageStatus.Info;
|
||||
message.Add($"{count} von {account.Transactions.Count} Transaktion(en) importiert.", status);
|
||||
|
||||
|
|
@ -109,8 +99,6 @@ namespace Schaad.Accounting.Services
|
|||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning("Skipping account {AccountNumber}: not part of the currently selected mandator ({Skipped} transactions ignored)",
|
||||
account.AccountNumber, account.Transactions.Count);
|
||||
message.Add($"Falscher Mandant: {account.Transactions.Count} Transaktion(en) nicht importiert.", MessageStatus.Warning);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
using Schaad.Accounting.Datasets;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Finance.Api;
|
||||
|
||||
namespace Schaad.Accounting.Services
|
||||
{
|
||||
public class FxConverter : IFxConverter
|
||||
{
|
||||
private readonly IFxService fxService;
|
||||
private readonly SettingsDataset settings;
|
||||
|
||||
public FxConverter(IFxService fxService, SettingsDataset settings)
|
||||
{
|
||||
this.fxService = fxService;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public decimal ConvertToChf(decimal amount, string fromCurrency)
|
||||
=> fxService.ConvertCurrency(amount, fromCurrency, "CHF", settings.FixerIoApiKey);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
namespace Schaad.Accounting.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Thin wrapper over the vendor <c>IFxService</c> that hides the FixerIo API key
|
||||
/// and the base currency, so callers just ask "convert this amount to CHF".
|
||||
/// </summary>
|
||||
public interface IFxConverter
|
||||
{
|
||||
decimal ConvertToChf(decimal amount, string fromCurrency);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FreeSpire.PDF" Version="10.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Schaad.Accounting.Common\Schaad.Accounting.Common.csproj" />
|
||||
|
|
|
|||
|
|
@ -10,13 +10,12 @@ namespace Schaad.Accounting.Services
|
|||
public class SettingsService : ISettingsService
|
||||
{
|
||||
private readonly SettingsDataset settingsDataset;
|
||||
private string mandator;
|
||||
private string mandator = "Claudio Schaad";
|
||||
private int year = DateTime.Now.Year;
|
||||
|
||||
public SettingsService(SettingsDataset settingsDataset)
|
||||
{
|
||||
this.settingsDataset = settingsDataset;
|
||||
this.mandator = settingsDataset.DefaultMandator;
|
||||
}
|
||||
|
||||
public int GetYear()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
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
|
||||
|
|
@ -17,8 +17,8 @@ namespace Schaad.Accounting.Services
|
|||
private readonly IBookingRuleRepository bookingRuleRepository;
|
||||
private readonly ISubclassRepository subclassRepository;
|
||||
private readonly ITransactionRepository transactionRepository;
|
||||
private readonly IFxConverter fxConverter;
|
||||
private readonly ILogger<ViewService> logger;
|
||||
private readonly IFxService fxService;
|
||||
private readonly ISettingsService settingsService;
|
||||
|
||||
public ViewService(
|
||||
IAccountRepository accountRepository,
|
||||
|
|
@ -26,16 +26,16 @@ namespace Schaad.Accounting.Services
|
|||
ITransactionRepository transactionRepository,
|
||||
ISubclassRepository subclassRepository,
|
||||
IBookingRuleRepository bookingRuleRepository,
|
||||
IFxConverter fxConverter,
|
||||
ILogger<ViewService> logger)
|
||||
IFxService fxService,
|
||||
ISettingsService settingsService)
|
||||
{
|
||||
this.accountRepository = accountRepository;
|
||||
this.bankTransactionRepository = bankTransactionRepository;
|
||||
this.transactionRepository = transactionRepository;
|
||||
this.subclassRepository = subclassRepository;
|
||||
this.bookingRuleRepository = bookingRuleRepository;
|
||||
this.fxConverter = fxConverter;
|
||||
this.logger = logger;
|
||||
this.fxService = fxService;
|
||||
this.settingsService = settingsService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -55,6 +55,7 @@ namespace Schaad.Accounting.Services
|
|||
var accounts = accountRepository.GetAccountList();
|
||||
var transactionList = GetTransactionViewList();
|
||||
var subClassNameByNumber = subclassRepository.GetSubClassList().ToDictionary(s => s.Number, s => s.Name);
|
||||
var settings = settingsService.GetSettings();
|
||||
|
||||
// Group transactions by account so per-account balance is O(k) instead of O(M).
|
||||
var creditsByAccount = transactionList.GroupBy(t => t.TargetAccountId).ToDictionary(g => g.Key, g => g.ToList());
|
||||
|
|
@ -66,8 +67,8 @@ namespace Schaad.Accounting.Services
|
|||
return new AccountDataset(
|
||||
account: a,
|
||||
balance: balance,
|
||||
balanceCHF: fxConverter.ConvertToChf(balance, a.Currency),
|
||||
startBalanceCHF: fxConverter.ConvertToChf(a.StartBalance, a.Currency),
|
||||
balanceCHF: fxService.ConvertCurrency(balance, a.Currency, "CHF", settings.FixerIoApiKey),
|
||||
startBalanceCHF: fxService.ConvertCurrency(a.StartBalance, a.Currency, "CHF", settings.FixerIoApiKey),
|
||||
className: subclassRepository.GetClass(a.Class),
|
||||
subClassName: subClassNameByNumber[a.SubClass]);
|
||||
})
|
||||
|
|
@ -110,8 +111,9 @@ namespace Schaad.Accounting.Services
|
|||
{
|
||||
var accountList = GetAccountViewList();
|
||||
|
||||
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 => fxConverter.ConvertToChf(m.StartBalance, m.Currency)));
|
||||
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(
|
||||
|
|
@ -163,7 +165,8 @@ namespace Schaad.Accounting.Services
|
|||
var originAccount = accountsById[t.OriginAccountId];
|
||||
if (originAccount.Class == ClassIds.Activa && accountId == t.OriginAccountId)
|
||||
{
|
||||
var copy = t.Clone();
|
||||
var copy = new Transaction();
|
||||
t.Copy(copy);
|
||||
copy.Value *= -1;
|
||||
return copy;
|
||||
}
|
||||
|
|
@ -208,7 +211,7 @@ namespace Schaad.Accounting.Services
|
|||
var transactions = transactionRepository.GetTransactionList();
|
||||
var accounts = accountRepository.GetAccountList();
|
||||
var bookingRules = bookingRuleRepository.GetBookingRuleList();
|
||||
var bankTransactions = GetOpenBankTransactionList().OrderBy(t => t.ValueDate).ToList();
|
||||
var bankTransactions = GetOpenBankTransactionList().OrderBy(t => t.ValueDate);
|
||||
foreach (var bankTransaction in bankTransactions)
|
||||
{
|
||||
var trx = new Transaction(bankTransaction, accounts);
|
||||
|
|
@ -217,9 +220,6 @@ namespace Schaad.Accounting.Services
|
|||
MatchBankTransactionBySameValueLastMonth(bankTransaction, trx, transactions);
|
||||
newTransactionList.Add(trx);
|
||||
}
|
||||
|
||||
var matched = newTransactionList.Count(t => string.IsNullOrEmpty(t.Text) == false);
|
||||
logger.LogInformation("Matched {Matched} of {Total} open bank transactions", matched, bankTransactions.Count);
|
||||
return newTransactionList;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NSubstitute;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
|
@ -22,7 +21,7 @@ public class AccountRepositoryTestShould : IDisposable
|
|||
settingsService.GetDbPath().Returns(dbDir);
|
||||
settingsService.GetLastYearDbPath().Returns(Path.Combine(dbDir, "no-such-dir"));
|
||||
|
||||
sut = new AccountRepository(settingsService, new RepositoryCache(), NullLogger<AccountRepository>.Instance);
|
||||
sut = new AccountRepository(settingsService, new RepositoryCache());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -104,7 +103,7 @@ public class AccountRepositoryTestShould : IDisposable
|
|||
sut.SaveAccount(new Account { Number = 1000, Name = "Cash", Currency = "CHF" });
|
||||
|
||||
// Re-construct with the same directory: existing file, no year rollover, no data loss.
|
||||
var fresh = new AccountRepository(settingsService, new RepositoryCache(), NullLogger<AccountRepository>.Instance);
|
||||
var fresh = new AccountRepository(settingsService, new RepositoryCache());
|
||||
|
||||
var accounts = fresh.GetAccountList();
|
||||
accounts.Count.ShouldBe(1);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System.Text;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NSubstitute;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
|
@ -27,7 +26,7 @@ public class FileServiceTestShould
|
|||
|
||||
private FileService BuildService() => new(
|
||||
settingsService, accountRepo, transactionRepo, bankTransactionRepo,
|
||||
accountStatementService, creditCardStatementService, NullLogger<FileService>.Instance);
|
||||
accountStatementService, creditCardStatementService);
|
||||
|
||||
[Fact]
|
||||
public void EmitHeaderRowAndRunningBalanceWhenExportingTransactionsCsv()
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
using NSubstitute;
|
||||
using Schaad.Accounting.Datasets;
|
||||
using Schaad.Accounting.Services;
|
||||
using Schaad.Finance.Api;
|
||||
using Shouldly;
|
||||
|
||||
namespace Schaad.Accounting.Tests;
|
||||
|
||||
public class FxConverterTestShould
|
||||
{
|
||||
private readonly IFxService fxService = Substitute.For<IFxService>();
|
||||
private readonly SettingsDataset settings = new() { DataPath = "", FixerIoApiKey = "test-key" };
|
||||
|
||||
private FxConverter BuildConverter() => new(fxService, settings);
|
||||
|
||||
[Fact]
|
||||
public void DelegateToFxServiceWithChfAsTargetAndConfiguredApiKeyWhenConverting()
|
||||
{
|
||||
fxService.ConvertCurrency(100m, "USD", "CHF", "test-key").Returns(90m);
|
||||
|
||||
var result = BuildConverter().ConvertToChf(100m, "USD");
|
||||
|
||||
result.ShouldBe(90m);
|
||||
fxService.Received(1).ConvertCurrency(100m, "USD", "CHF", "test-key");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PassNegativeAmountsThroughWhenConverting()
|
||||
{
|
||||
fxService.ConvertCurrency(-50m, "EUR", "CHF", Arg.Any<string>()).Returns(-48m);
|
||||
|
||||
BuildConverter().ConvertToChf(-50m, "EUR").ShouldBe(-48m);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NSubstitute;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
|
|
@ -26,7 +25,7 @@ public class TransactionRepositoryTestShould : IDisposable
|
|||
|
||||
accountRepo = Substitute.For<IAccountRepository>();
|
||||
cache = new RepositoryCache();
|
||||
sut = new TransactionRepository(settingsService, cache, accountRepo, NullLogger<TransactionRepository>.Instance);
|
||||
sut = new TransactionRepository(settingsService, cache, accountRepo);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -136,43 +135,4 @@ public class TransactionRepositoryTestShould : IDisposable
|
|||
|
||||
sut.GetTransactionList().ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreserveRelatedPartyWhenRoundTrippingTransaction()
|
||||
{
|
||||
accountRepo.GetAccount(Arg.Any<string>()).Returns(new Account { Currency = "CHF" });
|
||||
|
||||
sut.SaveTransaction(new Transaction
|
||||
{
|
||||
OriginAccountId = "a",
|
||||
TargetAccountId = "b",
|
||||
Value = 10m,
|
||||
Text = "Rent",
|
||||
RelatedParty = "ACME Property AG",
|
||||
ValueDate = new DateTime(2026, 4, 1),
|
||||
BookingDate = new DateTime(2026, 4, 1)
|
||||
});
|
||||
|
||||
// Regression: earlier Transaction.Copy dropped RelatedParty on every save,
|
||||
// so the round-trip lost it.
|
||||
sut.GetTransactionList().Single().RelatedParty.ShouldBe("ACME Property AG");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultBookingDateToValueDateWhenBookingDateIsUnset()
|
||||
{
|
||||
accountRepo.GetAccount(Arg.Any<string>()).Returns(new Account { Currency = "CHF" });
|
||||
|
||||
sut.SaveTransaction(new Transaction
|
||||
{
|
||||
OriginAccountId = "a",
|
||||
TargetAccountId = "b",
|
||||
Value = 10m,
|
||||
Text = "x",
|
||||
ValueDate = new DateTime(2026, 4, 15)
|
||||
// BookingDate left at default (DateTime.MinValue)
|
||||
});
|
||||
|
||||
sut.GetTransactionList().Single().BookingDate.ShouldBe(new DateTime(2026, 4, 15));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NSubstitute;
|
||||
using Schaad.Accounting.Datasets;
|
||||
using Schaad.Accounting.Interfaces;
|
||||
using Schaad.Accounting.Models;
|
||||
using Schaad.Accounting.Services;
|
||||
using Schaad.Finance.Api;
|
||||
using Shouldly;
|
||||
|
||||
namespace Schaad.Accounting.Tests;
|
||||
|
|
@ -14,7 +15,8 @@ public class ViewServiceTestShould
|
|||
private readonly IBankTransactionRepository bankTransactionRepo = Substitute.For<IBankTransactionRepository>();
|
||||
private readonly IBookingRuleRepository bookingRuleRepo = Substitute.For<IBookingRuleRepository>();
|
||||
private readonly ISubclassRepository subclassRepo = Substitute.For<ISubclassRepository>();
|
||||
private readonly IFxConverter fxConverter = Substitute.For<IFxConverter>();
|
||||
private readonly IFxService fxService = Substitute.For<IFxService>();
|
||||
private readonly ISettingsService settingsService = Substitute.For<ISettingsService>();
|
||||
|
||||
public ViewServiceTestShould()
|
||||
{
|
||||
|
|
@ -22,14 +24,15 @@ public class ViewServiceTestShould
|
|||
subclassRepo.GetSubClassList().Returns(
|
||||
Enumerable.Range(10, 50).Select(n => new SubClass { Number = n, Name = "sub-" + n }).ToList());
|
||||
subclassRepo.GetClass(Arg.Any<int>()).Returns("");
|
||||
settingsService.GetSettings().Returns(new SettingsDataset { DataPath = "", FixerIoApiKey = "" });
|
||||
|
||||
// Passthrough FX by default (CHF-only). Individual tests can override.
|
||||
fxConverter.ConvertToChf(Arg.Any<decimal>(), Arg.Any<string>())
|
||||
fxService.ConvertCurrency(Arg.Any<decimal>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>())
|
||||
.Returns(ci => ci.ArgAt<decimal>(0));
|
||||
}
|
||||
|
||||
private ViewService BuildService() =>
|
||||
new(accountRepo, bankTransactionRepo, transactionRepo, subclassRepo, bookingRuleRepo, fxConverter, NullLogger<ViewService>.Instance);
|
||||
new(accountRepo, bankTransactionRepo, transactionRepo, subclassRepo, bookingRuleRepo, fxService, settingsService);
|
||||
|
||||
// --- Balance math ---------------------------------------------------------
|
||||
|
||||
|
|
@ -79,7 +82,7 @@ public class ViewServiceTestShould
|
|||
transactionRepo.GetTransactionList().Returns(new List<Transaction>());
|
||||
|
||||
// 1 USD = 0.90 CHF
|
||||
fxConverter.ConvertToChf(Arg.Any<decimal>(), "USD")
|
||||
fxService.ConvertCurrency(Arg.Any<decimal>(), "USD", "CHF", Arg.Any<string>())
|
||||
.Returns(ci => ci.ArgAt<decimal>(0) * 0.9m);
|
||||
|
||||
var account = BuildService().GetAccountViewList().Single();
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ public partial class Home : ComponentBase
|
|||
[Inject]
|
||||
private IDialogService dialogService { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ISettingsService settingsService { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IFileService fileService { get; set; } = null!;
|
||||
|
||||
|
|
@ -44,11 +47,17 @@ public partial class Home : ComponentBase
|
|||
bool IsCanceled;
|
||||
Dictionary<int, string> Files = new();
|
||||
|
||||
private bool loaded;
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
matchingBankTransactions = viewService.MatchOpenBankTransactions();
|
||||
accounts = viewService.GetAccountViewList();
|
||||
bookingTexts = bookingTextRepository.GetBookingTextList().Select(b => b.Text).ToArray();
|
||||
if (loaded == false) {
|
||||
matchingBankTransactions = viewService.MatchOpenBankTransactions();
|
||||
accounts = viewService.GetAccountViewList();
|
||||
bookingTexts = bookingTextRepository.GetBookingTextList().Select(b => b.Text).ToArray();
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ namespace Schaad.Accounting.UI
|
|||
services.AddScoped<IAccountStatementService, AccountStatementService>();
|
||||
services.AddScoped<ICreditCardStatementService, CreditCardStatementService>();
|
||||
services.AddSingleton<IFxService, DummyFxService>();
|
||||
services.AddScoped<IFxConverter, FxConverter>();
|
||||
services.AddSingleton<IPdfParsingService, PdfParsingService>();
|
||||
|
||||
return services;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
},
|
||||
"Settings": {
|
||||
"DataPath": "D:\\Developer\\AccountingData\\",
|
||||
"FixerIoApiKey": "ee23142f1120bb1223b0e76f98abfa97",
|
||||
"DefaultMandator": "Claudio Schaad"
|
||||
"FixerIoApiKey": "ee23142f1120bb1223b0e76f98abfa97"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue