diff --git a/IMPROVEMENT_PLAN.md b/IMPROVEMENT_PLAN.md index da19b31..ab9f5ae 100644 --- a/IMPROVEMENT_PLAN.md +++ b/IMPROVEMENT_PLAN.md @@ -97,6 +97,7 @@ Analysis and phased plan produced 2026-07-02. See conversation history for full - **PR A** — Phase 1 items 1–7 + CLAUDE.md fix. - **PR B** — Phase 1 item 8 (atomic writes + lock). -- **PR C** — Phase 2 items 9 + 11 + 13 + 15 + 16 + 17. -- **PR D** — Phase 3 (async). -- **PR E** — Phase 4 (tests) — done alongside PR C to lock in behavior. +- **PR C** — Phase 2 mechanical cleanup: items 11, 13, 15, 16, 17. +- **PR D** — Phase 2 item 9: per-request unit of work / cache. Split off from PR C because it is invasive enough to warrant its own review. +- **PR E** — Phase 3 (async). +- **PR F** — Phase 4 (tests) — done alongside PR D to lock in behavior. diff --git a/Schaad.Accounting.Common/Formatting.cs b/Schaad.Accounting.Common/Formatting.cs new file mode 100644 index 0000000..58536d1 --- /dev/null +++ b/Schaad.Accounting.Common/Formatting.cs @@ -0,0 +1,19 @@ +using System.Globalization; + +namespace Schaad.Accounting +{ + public static class Formatting + { + private static readonly CultureInfo SwissCulture = CreateSwissCulture(); + + public static string ToFormattedString(this decimal value) + => value.ToString("#,0.00", SwissCulture); + + private static CultureInfo CreateSwissCulture() + { + var culture = new CultureInfo("de-CH"); + culture.NumberFormat.NumberGroupSeparator = "'"; + return culture; + } + } +} diff --git a/Schaad.Accounting.Common/Interfaces/ISplitPredefinitonRepository.cs b/Schaad.Accounting.Common/Interfaces/ISplitPredefinitionRepository.cs similarity index 71% rename from Schaad.Accounting.Common/Interfaces/ISplitPredefinitonRepository.cs rename to Schaad.Accounting.Common/Interfaces/ISplitPredefinitionRepository.cs index d635881..151ec8e 100644 --- a/Schaad.Accounting.Common/Interfaces/ISplitPredefinitonRepository.cs +++ b/Schaad.Accounting.Common/Interfaces/ISplitPredefinitionRepository.cs @@ -1,12 +1,12 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Schaad.Accounting.Models; namespace Schaad.Accounting.Interfaces { - public interface ISplitPredefinitonRepository + public interface ISplitPredefinitionRepository { List GetSplitPredefinitionList(); void SaveSplitPredefinition(SplitPredefinition splitPredefinition); } -} \ No newline at end of file +} diff --git a/Schaad.Accounting.Common/Schaad.Accounting.Common.csproj b/Schaad.Accounting.Common/Schaad.Accounting.Common.csproj index 47844fe..20fe4e1 100644 --- a/Schaad.Accounting.Common/Schaad.Accounting.Common.csproj +++ b/Schaad.Accounting.Common/Schaad.Accounting.Common.csproj @@ -4,17 +4,9 @@ net9.0 Schaad.Accounting - - - - - ..\dlls\Schaad.Finance.Api.dll - - - \ No newline at end of file diff --git a/Schaad.Accounting.Db/Repositories/AccountRepository.cs b/Schaad.Accounting.Db/Repositories/AccountRepository.cs index d96683e..1061dc9 100644 --- a/Schaad.Accounting.Db/Repositories/AccountRepository.cs +++ b/Schaad.Accounting.Db/Repositories/AccountRepository.cs @@ -13,7 +13,7 @@ namespace Schaad.Accounting.Repositories public AccountRepository(ISettingsService settingsService) : base(settingsService) { - EnsureFileExisits(ACCOUNTS); + EnsureAccountsFile(); } @@ -79,35 +79,40 @@ namespace Schaad.Accounting.Repositories SaveAccount(account); } - private new void EnsureFileExisits(string file) + private void EnsureAccountsFile() { - string filePath = Path.Combine(settingsService.GetDbPath(), file); - if (File.Exists(filePath) == false) + string filePath = Path.Combine(settingsService.GetDbPath(), ACCOUNTS); + if (File.Exists(filePath)) { - var lastYearFile = Path.Combine(settingsService.GetLastYearDbPath(), file); - if (File.Exists(lastYearFile)) - File.Copy(lastYearFile, filePath); + return; + } - // set start balance to last bank balance - if (file.IndexOf("Accounts") > -1) + var lastYearFile = Path.Combine(settingsService.GetLastYearDbPath(), ACCOUNTS); + if (File.Exists(lastYearFile)) + { + File.Copy(lastYearFile, filePath); + } + + // Year rollover: seed start balances from the previous year's closing values. + // Bank accounts start at the last known bank balance; cash accounts start at zero. + var accounts = GetAccountList(); + var modified = false; + foreach (var account in accounts.Where(a => a.Class == ClassIds.Activa)) + { + if (account.LastBankBalance > 0) { - var accounts = GetAccountList(); - foreach (var account in accounts.Where(a => a.Class == ClassIds.Activa)) - { - // bank accounts - if (account.LastBankBalance > 0) - { - account.StartBalance = account.LastBankBalance; - SaveAccount(account); - } - // cash accounts - else if (account.Number < 1010) - { - account.StartBalance = 0; - SaveAccount(account); - } - } + account.StartBalance = account.LastBankBalance; + modified = true; } + else if (account.Number < 1010) + { + account.StartBalance = 0; + modified = true; + } + } + if (modified) + { + Save(accounts, ACCOUNTS); } } } diff --git a/Schaad.Accounting.Db/Repositories/BaseRepository.cs b/Schaad.Accounting.Db/Repositories/BaseRepository.cs index 9f727d8..4411e15 100644 --- a/Schaad.Accounting.Db/Repositories/BaseRepository.cs +++ b/Schaad.Accounting.Db/Repositories/BaseRepository.cs @@ -22,7 +22,7 @@ namespace Schaad.Accounting.Repositories this.settingsService = settingsService; } - protected void EnsureFileExisits(string fileName) + protected void EnsureFileExists(string fileName) { string filePath = Path.Combine(settingsService.GetDbPath(), fileName); lock (GetLock(filePath)) diff --git a/Schaad.Accounting.Db/Repositories/BookingRuleRepository.cs b/Schaad.Accounting.Db/Repositories/BookingRuleRepository.cs index 3e3fd81..619fce4 100644 --- a/Schaad.Accounting.Db/Repositories/BookingRuleRepository.cs +++ b/Schaad.Accounting.Db/Repositories/BookingRuleRepository.cs @@ -12,7 +12,7 @@ namespace Schaad.Accounting.Repositories public BookingRuleRepository(ISettingsService settingsService) : base(settingsService) { - EnsureFileExisits(BOOKING_RULES); + EnsureFileExists(BOOKING_RULES); } /// diff --git a/Schaad.Accounting.Db/Repositories/BookingTextRepository.cs b/Schaad.Accounting.Db/Repositories/BookingTextRepository.cs index 2844db3..d3fa0c7 100644 --- a/Schaad.Accounting.Db/Repositories/BookingTextRepository.cs +++ b/Schaad.Accounting.Db/Repositories/BookingTextRepository.cs @@ -12,7 +12,7 @@ namespace Schaad.Accounting.Repositories public BookingTextRepository(ISettingsService settingsService) : base(settingsService) { - EnsureFileExisits(BOOKING_TEXTS); + EnsureFileExists(BOOKING_TEXTS); } /// diff --git a/Schaad.Accounting.Db/Repositories/SplitPredefinitonRepository.cs b/Schaad.Accounting.Db/Repositories/SplitPredefinitionRepository.cs similarity index 77% rename from Schaad.Accounting.Db/Repositories/SplitPredefinitonRepository.cs rename to Schaad.Accounting.Db/Repositories/SplitPredefinitionRepository.cs index 47c716e..ac80d29 100644 --- a/Schaad.Accounting.Db/Repositories/SplitPredefinitonRepository.cs +++ b/Schaad.Accounting.Db/Repositories/SplitPredefinitionRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Schaad.Accounting.Interfaces; @@ -6,17 +6,17 @@ using Schaad.Accounting.Models; namespace Schaad.Accounting.Repositories { - public class SplitPredefinitonRepository : BaseRepository, ISplitPredefinitonRepository + public class SplitPredefinitionRepository : BaseRepository, ISplitPredefinitionRepository { private readonly string SPLIT_PREDEFINITION = "SplitPredefinitions.xml"; - public SplitPredefinitonRepository(ISettingsService settingsService) : base(settingsService) + public SplitPredefinitionRepository(ISettingsService settingsService) : base(settingsService) { - EnsureFileExisits(SPLIT_PREDEFINITION); + EnsureFileExists(SPLIT_PREDEFINITION); } /// - /// Load booking rules + /// Load split predefinitions /// public List GetSplitPredefinitionList() { @@ -25,7 +25,7 @@ namespace Schaad.Accounting.Repositories } /// - /// Save a booking rule (insert/update) + /// Save a split predefinition (insert/update) /// public void SaveSplitPredefinition(SplitPredefinition splitPredefinition) { @@ -42,4 +42,4 @@ namespace Schaad.Accounting.Repositories Save(definitions, SPLIT_PREDEFINITION); } } -} \ No newline at end of file +} diff --git a/Schaad.Accounting.Db/Repositories/SubclassRepository.cs b/Schaad.Accounting.Db/Repositories/SubclassRepository.cs index 55762fb..f2ecb7c 100644 --- a/Schaad.Accounting.Db/Repositories/SubclassRepository.cs +++ b/Schaad.Accounting.Db/Repositories/SubclassRepository.cs @@ -13,7 +13,7 @@ namespace Schaad.Accounting.Repositories public SubclassRepository(ISettingsService settingsService) : base(settingsService) { - EnsureFileExisits(SUBCLASSES); + EnsureFileExists(SUBCLASSES); classes.Add(ClassIds.Activa, "Aktiven"); classes.Add(ClassIds.Passiva, "Passiven"); diff --git a/Schaad.Accounting.Db/Schaad.Accounting.Db.csproj b/Schaad.Accounting.Db/Schaad.Accounting.Db.csproj index bed63a7..8f28362 100644 --- a/Schaad.Accounting.Db/Schaad.Accounting.Db.csproj +++ b/Schaad.Accounting.Db/Schaad.Accounting.Db.csproj @@ -4,9 +4,6 @@ net9.0 - - - diff --git a/Schaad.Accounting.Services/FileService.cs b/Schaad.Accounting.Services/FileService.cs index 57f24b4..e6b3b8f 100644 --- a/Schaad.Accounting.Services/FileService.cs +++ b/Schaad.Accounting.Services/FileService.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Globalization; using System.IO.Compression; using System.Linq; using System.Text; @@ -134,25 +133,18 @@ namespace Schaad.Accounting.Services if (trx.OriginAccountId == accountId) { - debit = ToFormattedString(trx.Value); + debit = trx.Value.ToFormattedString(); signedValue = -trx.Value; } else { - credit = ToFormattedString(trx.Value); + credit = trx.Value.ToFormattedString(); } balance += signedValue; - sb.AppendLine($"{trx.BookingDate:dd.MM.yyyy};{trx.ValueDate:dd.MM.yyyy};{trx.Text};{debit};{credit};{ToFormattedString(balance)}"); + sb.AppendLine($"{trx.BookingDate:dd.MM.yyyy};{trx.ValueDate:dd.MM.yyyy};{trx.Text};{debit};{credit};{balance.ToFormattedString()}"); } var fileBytes = Encoding.GetEncoding("ISO-8859-1").GetBytes(sb.ToString()); return fileBytes; } - - private string ToFormattedString(decimal value) - { - var culture = new CultureInfo("de-CH"); - culture.NumberFormat.NumberGroupSeparator = "'"; - return value.ToString("#,0.00", culture); - } } } \ No newline at end of file diff --git a/Schaad.Accounting.Services/Schaad.Accounting.Services.csproj b/Schaad.Accounting.Services/Schaad.Accounting.Services.csproj index 8bc3a30..2def6e6 100644 --- a/Schaad.Accounting.Services/Schaad.Accounting.Services.csproj +++ b/Schaad.Accounting.Services/Schaad.Accounting.Services.csproj @@ -6,7 +6,6 @@ - diff --git a/Schaad.Accounting.UI/Extensions.cs b/Schaad.Accounting.UI/Extensions.cs index 22f714d..b564a45 100644 --- a/Schaad.Accounting.UI/Extensions.cs +++ b/Schaad.Accounting.UI/Extensions.cs @@ -1,5 +1,4 @@ -using System.Globalization; -using Schaad.Accounting.Interfaces; +using Schaad.Accounting.Interfaces; using Schaad.Accounting.Repositories; using Schaad.Accounting.Services; using Schaad.Finance.Api; @@ -9,13 +8,6 @@ namespace Schaad.Accounting.UI { public static class Extensions { - public static string ToFormattedString(this decimal value) - { - var culture = new CultureInfo("de-CH"); - culture.NumberFormat.NumberGroupSeparator = "'"; - return value.ToString("#,0.00", culture); - } - public static IServiceCollection AddAccounting(this IServiceCollection services) { // Singleton because MyHeader triggers a full page reload (forceLoad: true) after @@ -28,7 +20,7 @@ namespace Schaad.Accounting.UI services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/Schaad.Accounting.UI/Program.cs b/Schaad.Accounting.UI/Program.cs index e570cc7..5475d30 100644 --- a/Schaad.Accounting.UI/Program.cs +++ b/Schaad.Accounting.UI/Program.cs @@ -1,4 +1,5 @@ using System.Globalization; +using Microsoft.AspNetCore.Localization; using Microsoft.Extensions.Options; using Microsoft.FluentUI.AspNetCore.Components; using Schaad.Accounting.Datasets; @@ -38,15 +39,19 @@ namespace Schaad.Accounting.UI app.UseHttpsRedirection(); + var supportedCultures = new[] { new CultureInfo("de-CH") }; + app.UseRequestLocalization(new RequestLocalizationOptions + { + DefaultRequestCulture = new RequestCulture(supportedCultures[0]), + SupportedCultures = supportedCultures, + SupportedUICultures = supportedCultures + }); + app.UseAntiforgery(); app.MapStaticAssets(); app.MapRazorComponents() .AddInteractiveServerRenderMode(); - - var culture = new CultureInfo("de-CH"); - CultureInfo.DefaultThreadCurrentCulture = culture; - CultureInfo.DefaultThreadCurrentUICulture = culture; - + app.Run(); } }